diff options
Diffstat (limited to 'tests/DynamicCodeLoggerIntegrationTests')
7 files changed, 710 insertions, 0 deletions
diff --git a/tests/DynamicCodeLoggerIntegrationTests/Android.mk b/tests/DynamicCodeLoggerIntegrationTests/Android.mk new file mode 100644 index 000000000000..f324eb10a7b0 --- /dev/null +++ b/tests/DynamicCodeLoggerIntegrationTests/Android.mk @@ -0,0 +1,84 @@ +# +# Copyright 2017 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) + +# Build a tiny library that the test app can dynamically load + +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := tests +LOCAL_MODULE := DynamicCodeLoggerTestLibrary +LOCAL_SRC_FILES := $(call all-java-files-under, src/com/android/dcl) + +include $(BUILD_JAVA_LIBRARY) + +dynamiccodeloggertest_jar := $(LOCAL_BUILT_MODULE) + + +# Also build a native library that the test app can dynamically load + +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := tests +LOCAL_MODULE := DynamicCodeLoggerNativeTestLibrary +LOCAL_SRC_FILES := src/cpp/com_android_dcl_Jni.cpp +LOCAL_C_INCLUDES += \ + $(JNI_H_INCLUDE) +LOCAL_SDK_VERSION := 28 +LOCAL_NDK_STL_VARIANT := c++_static + +include $(BUILD_SHARED_LIBRARY) + +# And a standalone native executable that we can exec. + +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := tests +LOCAL_MODULE := DynamicCodeLoggerNativeExecutable +LOCAL_SRC_FILES := src/cpp/test_executable.cpp + +include $(BUILD_EXECUTABLE) + +dynamiccodeloggertest_executable := $(LOCAL_BUILT_MODULE) + +# Build the test app itself + +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := tests +LOCAL_PACKAGE_NAME := DynamicCodeLoggerIntegrationTests +LOCAL_SDK_VERSION := current +LOCAL_COMPATIBILITY_SUITE := device-tests +LOCAL_CERTIFICATE := shared +LOCAL_SRC_FILES := $(call all-java-files-under, src/com/android/server/pm) + +LOCAL_STATIC_JAVA_LIBRARIES := \ + android-support-test \ + truth-prebuilt \ + +# Include both versions of the .so if we have 2 arch +LOCAL_MULTILIB := both +LOCAL_JNI_SHARED_LIBRARIES := \ + DynamicCodeLoggerNativeTestLibrary \ + +# This gets us the javalib.jar built by DynamicCodeLoggerTestLibrary above as well as the various +# native binaries. +LOCAL_JAVA_RESOURCE_FILES := \ + $(dynamiccodeloggertest_jar) \ + $(dynamiccodeloggertest_executable) \ + +include $(BUILD_PACKAGE) diff --git a/tests/DynamicCodeLoggerIntegrationTests/AndroidManifest.xml b/tests/DynamicCodeLoggerIntegrationTests/AndroidManifest.xml new file mode 100644 index 000000000000..4327da2db3dd --- /dev/null +++ b/tests/DynamicCodeLoggerIntegrationTests/AndroidManifest.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright 2017 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.frameworks.dynamiccodeloggertest"> + + <!-- Tests feature introduced in P (28) --> + <uses-sdk + android:minSdkVersion="28" + android:targetSdkVersion="28" /> + + <uses-permission android:name="android.permission.READ_LOGS" /> + + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation + android:name="android.support.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.frameworks.dynamiccodeloggertest" + android:label="Integration test for DynamicCodeLogger" /> +</manifest> diff --git a/tests/DynamicCodeLoggerIntegrationTests/AndroidTest.xml b/tests/DynamicCodeLoggerIntegrationTests/AndroidTest.xml new file mode 100644 index 000000000000..f70b9c8cb357 --- /dev/null +++ b/tests/DynamicCodeLoggerIntegrationTests/AndroidTest.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright 2017 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<configuration description="Runs DynamicLogger Integration Tests"> + <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup"> + <option name="test-file-name" value="DynamicCodeLoggerIntegrationTests.apk"/> + <option name="cleanup-apks" value="true"/> + </target_preparer> + + <option name="test-suite-tag" value="apct"/> + <option name="test-tag" value="DynamicCodeLoggerIntegrationTests"/> + + <test class="com.android.tradefed.testtype.AndroidJUnitTest"> + <option name="package" value="com.android.frameworks.dynamiccodeloggertest"/> + <option name="runner" value="android.support.test.runner.AndroidJUnitRunner"/> + <option name="hidden-api-checks" value="false"/> + </test> +</configuration> diff --git a/tests/DynamicCodeLoggerIntegrationTests/src/com/android/dcl/Simple.java b/tests/DynamicCodeLoggerIntegrationTests/src/com/android/dcl/Simple.java new file mode 100644 index 000000000000..e995a26ea5c9 --- /dev/null +++ b/tests/DynamicCodeLoggerIntegrationTests/src/com/android/dcl/Simple.java @@ -0,0 +1,22 @@ +/* + * Copyright 2017 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.dcl; + +/** Dummy class which is built into a jar purely so we can pass it to DexClassLoader. */ +public final class Simple { + public Simple() {} +} diff --git a/tests/DynamicCodeLoggerIntegrationTests/src/com/android/server/pm/dex/DynamicCodeLoggerIntegrationTests.java b/tests/DynamicCodeLoggerIntegrationTests/src/com/android/server/pm/dex/DynamicCodeLoggerIntegrationTests.java new file mode 100644 index 000000000000..8ef15d869a0b --- /dev/null +++ b/tests/DynamicCodeLoggerIntegrationTests/src/com/android/server/pm/dex/DynamicCodeLoggerIntegrationTests.java @@ -0,0 +1,497 @@ +/* + * Copyright 2017 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.server.pm.dex; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import android.app.UiAutomation; +import android.content.Context; +import android.os.Build; +import android.os.ParcelFileDescriptor; +import android.os.SystemClock; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.LargeTest; +import android.util.EventLog; +import android.util.EventLog.Event; + +import dalvik.system.DexClassLoader; + +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.MessageDigest; +import java.util.ArrayList; +import java.util.Formatter; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * Integration tests for {@link DynamicCodeLogger}. + * + * The setup for the test dynamically loads code in a jar extracted + * from our assets (a secondary dex file). + * + * We then use shell commands to trigger dynamic code logging (and wait + * for it to complete). This causes DynamicCodeLogger to log the hash of the + * file's name and content. We verify that this message appears in + * the event log. + * + * Run with "atest DynamicCodeLoggerIntegrationTests". + */ +@LargeTest +@RunWith(JUnit4.class) +public final class DynamicCodeLoggerIntegrationTests { + + private static final String SHA_256 = "SHA-256"; + + // Event log tag used for SNET related events + private static final int SNET_TAG = 0x534e4554; + + // Subtags used to distinguish dynamic code loading events + private static final String DCL_DEX_SUBTAG = "dcl"; + private static final String DCL_NATIVE_SUBTAG = "dcln"; + + // These are job IDs from DynamicCodeLoggingService + private static final int IDLE_LOGGING_JOB_ID = 2030028; + private static final int AUDIT_WATCHING_JOB_ID = 203142925; + + // For tests that rely on parsing audit logs, how often to retry. (There are many reasons why + // we might not see the audit logs, including throttling and delays in log generation, so to + // avoid flakiness we run these tests multiple times, allowing progressively longer between + // code loading and checking the logs on each try.) + private static final int AUDIT_LOG_RETRIES = 10; + private static final int RETRY_DELAY_MS = 2_000; + + private static Context sContext; + private static int sMyUid; + + @BeforeClass + public static void setUpAll() { + sContext = InstrumentationRegistry.getTargetContext(); + sMyUid = android.os.Process.myUid(); + } + + @Before + public void primeEventLog() { + // Force a round trip to logd to make sure everything is up to date. + // Without this the first test passes and others don't - we don't see new events in the + // log. The exact reason is unclear. + EventLog.writeEvent(SNET_TAG, "Dummy event"); + + // Audit log messages are throttled by the kernel (at the request of logd) to 5 per + // second, so running the tests too quickly in sequence means we lose some and get + // spurious failures. Sigh. + SystemClock.sleep(1000); + } + + @Test + public void testGeneratesEvents_standardClassLoader() throws Exception { + File privateCopyFile = privateFile("copied.jar"); + // Obtained via "echo -n copied.jar | sha256sum" + String expectedNameHash = + "1B6C71DB26F36582867432CCA12FB6A517470C9F9AABE9198DD4C5C030D6DC0C"; + String expectedContentHash = copyAndHashResource("/javalib.jar", privateCopyFile); + + // Feed the jar to a class loader and make sure it contains what we expect. + ClassLoader parentClassLoader = sContext.getClass().getClassLoader(); + ClassLoader loader = + new DexClassLoader(privateCopyFile.toString(), null, null, parentClassLoader); + loader.loadClass("com.android.dcl.Simple"); + + // And make sure we log events about it + long previousEventNanos = mostRecentEventTimeNanos(); + runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID); + + assertDclLoggedSince(previousEventNanos, DCL_DEX_SUBTAG, + expectedNameHash, expectedContentHash); + } + + @Test + public void testGeneratesEvents_unknownClassLoader() throws Exception { + File privateCopyFile = privateFile("copied2.jar"); + String expectedNameHash = + "202158B6A3169D78F1722487205A6B036B3F2F5653FDCFB4E74710611AC7EB93"; + String expectedContentHash = copyAndHashResource("/javalib.jar", privateCopyFile); + + // This time make sure an unknown class loader is an ancestor of the class loader we use. + ClassLoader knownClassLoader = sContext.getClass().getClassLoader(); + ClassLoader unknownClassLoader = new UnknownClassLoader(knownClassLoader); + ClassLoader loader = + new DexClassLoader(privateCopyFile.toString(), null, null, unknownClassLoader); + loader.loadClass("com.android.dcl.Simple"); + + // And make sure we log events about it + long previousEventNanos = mostRecentEventTimeNanos(); + runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID); + + assertDclLoggedSince(previousEventNanos, DCL_DEX_SUBTAG, + expectedNameHash, expectedContentHash); + } + + @Test + public void testGeneratesEvents_nativeLibrary() throws Exception { + new TestNativeCodeWithRetries() { + @Override + protected void loadNativeCode(int tryNumber) throws Exception { + // We need to use a different file name for each retry, because once a file is + // loaded, re-loading it has no effect. + String privateCopyName = "copied" + tryNumber + ".so"; + File privateCopyFile = privateFile(privateCopyName); + mExpectedNameHash = hashOf(privateCopyName); + mExpectedContentHash = copyAndHashResource( + libraryPath("DynamicCodeLoggerNativeTestLibrary.so"), privateCopyFile); + + System.load(privateCopyFile.toString()); + } + }.runTest(); + } + + @Test + public void testGeneratesEvents_nativeLibrary_escapedName() throws Exception { + new TestNativeCodeWithRetries() { + @Override + protected void loadNativeCode(int tryNumber) throws Exception { + // A file name with a space will be escaped in the audit log; verify we un-escape it + // correctly. + String privateCopyName = "second copy " + tryNumber + ".so"; + File privateCopyFile = privateFile(privateCopyName); + mExpectedNameHash = hashOf(privateCopyName); + mExpectedContentHash = copyAndHashResource( + libraryPath("DynamicCodeLoggerNativeTestLibrary.so"), privateCopyFile); + + System.load(privateCopyFile.toString()); + } + }.runTest(); + } + + @Test + public void testGeneratesEvents_nativeExecutable() throws Exception { + new TestNativeCodeWithRetries() { + @Override + protected void loadNativeCode(int tryNumber) throws Exception { + String privateCopyName = "test_executable" + tryNumber; + File privateCopyFile = privateFile(privateCopyName); + mExpectedNameHash = hashOf(privateCopyName); + mExpectedContentHash = copyAndHashResource( + "/DynamicCodeLoggerNativeExecutable", privateCopyFile); + assertThat(privateCopyFile.setExecutable(true)).isTrue(); + + Process process = Runtime.getRuntime().exec(privateCopyFile.toString()); + int exitCode = process.waitFor(); + assertThat(exitCode).isEqualTo(0); + } + }.runTest(); + } + + @Test + public void testGeneratesEvents_spoofed_validFile() throws Exception { + File privateCopyFile = privateFile("spoofed"); + + String expectedContentHash = copyAndHashResource( + "/DynamicCodeLoggerNativeExecutable", privateCopyFile); + + EventLog.writeEvent(EventLog.getTagCode("auditd"), + "type=1400 avc: granted { execute_no_trans } " + + "path=\"" + privateCopyFile + "\" " + + "scontext=u:r:untrusted_app_27: " + + "tcontext=u:object_r:app_data_file: " + + "tclass=file "); + + String expectedNameHash = + "1CF36F503A02877BB775DC23C1C5A47A95F2684B6A1A83B11795B856D88861E3"; + + // Run the job to scan generated audit log entries + runDynamicCodeLoggingJob(AUDIT_WATCHING_JOB_ID); + + // And then make sure we log events about it + long previousEventNanos = mostRecentEventTimeNanos(); + runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID); + + assertDclLoggedSince(previousEventNanos, DCL_NATIVE_SUBTAG, + expectedNameHash, expectedContentHash); + } + + @Test + public void testGeneratesEvents_spoofed_pathTraversal() throws Exception { + File privateDir = privateFile("x").getParentFile(); + + // Transform /a/b/c -> /a/b/c/../../.. so we get back to the root + File pathTraversalToRoot = privateDir; + File root = new File("/"); + while (!privateDir.equals(root)) { + pathTraversalToRoot = new File(pathTraversalToRoot, ".."); + privateDir = privateDir.getParentFile(); + } + + File spoofedFile = new File(pathTraversalToRoot, "dev/urandom"); + + assertWithMessage("Expected " + spoofedFile + " to be readable") + .that(spoofedFile.canRead()).isTrue(); + + EventLog.writeEvent(EventLog.getTagCode("auditd"), + "type=1400 avc: granted { execute_no_trans } " + + "path=\"" + spoofedFile + "\" " + + "scontext=u:r:untrusted_app_27: " + + "tcontext=u:object_r:app_data_file: " + + "tclass=file "); + + String expectedNameHash = + "65528FE876BD676B0DFCC9A8ACA8988E026766F99EEC1E1FB48F46B2F635E225"; + + // Run the job to scan generated audit log entries + runDynamicCodeLoggingJob(AUDIT_WATCHING_JOB_ID); + + // And then trigger generating DCL events + long previousEventNanos = mostRecentEventTimeNanos(); + runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID); + + assertNoDclLoggedSince(previousEventNanos, DCL_NATIVE_SUBTAG, expectedNameHash); + } + + @Test + public void testGeneratesEvents_spoofed_otherAppFile() throws Exception { + File ourPath = sContext.getDatabasePath("android_pay"); + File targetPath = new File(ourPath.toString() + .replace("com.android.frameworks.dynamiccodeloggertest", "com.google.android.gms")); + + assertWithMessage("Expected " + targetPath + " to not be readable") + .that(targetPath.canRead()).isFalse(); + + EventLog.writeEvent(EventLog.getTagCode("auditd"), + "type=1400 avc: granted { execute_no_trans } " + + "path=\"" + targetPath + "\" " + + "scontext=u:r:untrusted_app_27: " + + "tcontext=u:object_r:app_data_file: " + + "tclass=file "); + + String expectedNameHash = + "CBE04E8AB9E7199FC19CBAAF9C774B88E56B3B19E823F2251693380AD6F515E6"; + + // Run the job to scan generated audit log entries + runDynamicCodeLoggingJob(AUDIT_WATCHING_JOB_ID); + + // And then trigger generating DCL events + long previousEventNanos = mostRecentEventTimeNanos(); + runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID); + + assertNoDclLoggedSince(previousEventNanos, DCL_NATIVE_SUBTAG, expectedNameHash); + } + + // Abstract out the logic for running a native code loading test multiple times if needed and + // leaving time for audit messages to reach the log. + private abstract class TestNativeCodeWithRetries { + String mExpectedContentHash; + String mExpectedNameHash; + + abstract void loadNativeCode(int tryNumber) throws Exception; + + final void runTest() throws Exception { + List<String> messages = null; + + for (int i = 0; i < AUDIT_LOG_RETRIES; i++) { + loadNativeCode(i); + + SystemClock.sleep(i * RETRY_DELAY_MS); + + // Run the job to scan generated audit log entries + runDynamicCodeLoggingJob(AUDIT_WATCHING_JOB_ID); + + // And then make sure we log events about it + long previousEventNanos = mostRecentEventTimeNanos(); + runDynamicCodeLoggingJob(IDLE_LOGGING_JOB_ID); + + messages = findMatchingEvents( + previousEventNanos, DCL_NATIVE_SUBTAG, mExpectedNameHash); + if (!messages.isEmpty()) { + break; + } + } + + assertHasDclLog(messages, mExpectedContentHash); + } + } + + private static File privateFile(String name) { + return new File(sContext.getDir("dcl", Context.MODE_PRIVATE), name); + } + + private String libraryPath(final String libraryName) { + // This may be deprecated. but it tells us the ABI of this process which is exactly what we + // want. + return "/lib/" + Build.CPU_ABI + "/" + libraryName; + } + + private static String copyAndHashResource(String resourcePath, File copyTo) throws Exception { + MessageDigest hasher = MessageDigest.getInstance(SHA_256); + + // Copy the jar from our Java resources to a private data directory + Class<?> thisClass = DynamicCodeLoggerIntegrationTests.class; + try (InputStream input = thisClass.getResourceAsStream(resourcePath); + OutputStream output = new FileOutputStream(copyTo)) { + byte[] buffer = new byte[1024]; + while (true) { + int numRead = input.read(buffer); + if (numRead < 0) { + break; + } + output.write(buffer, 0, numRead); + hasher.update(buffer, 0, numRead); + } + } + + // Compute the SHA-256 of the file content so we can check that it is the same as the value + // we see logged. + return toHexString(hasher); + } + + private String hashOf(String input) throws Exception { + MessageDigest hasher = MessageDigest.getInstance(SHA_256); + hasher.update(input.getBytes()); + return toHexString(hasher); + } + + private static String toHexString(MessageDigest hasher) { + Formatter formatter = new Formatter(); + for (byte b : hasher.digest()) { + formatter.format("%02X", b); + } + + return formatter.toString(); + } + + private static void runDynamicCodeLoggingJob(int jobId) throws Exception { + // This forces the DynamicCodeLoggingService job to start now. + runCommand("cmd jobscheduler run -f android " + jobId); + // Wait for the job to have run. + long startTime = SystemClock.elapsedRealtime(); + while (true) { + String response = runCommand( + "cmd jobscheduler get-job-state android " + jobId); + if (!response.contains("pending") && !response.contains("active")) { + break; + } + // Don't wait forever - if it's taken > 10s then something is very wrong. + if (SystemClock.elapsedRealtime() - startTime > TimeUnit.SECONDS.toMillis(10)) { + throw new AssertionError("Job has not completed: " + response); + } + SystemClock.sleep(100); + } + } + + private static String runCommand(String command) throws Exception { + ByteArrayOutputStream response = new ByteArrayOutputStream(); + byte[] buffer = new byte[1000]; + UiAutomation ui = InstrumentationRegistry.getInstrumentation().getUiAutomation(); + ParcelFileDescriptor fd = ui.executeShellCommand(command); + try (InputStream input = new ParcelFileDescriptor.AutoCloseInputStream(fd)) { + while (true) { + int count = input.read(buffer); + if (count == -1) { + break; + } + response.write(buffer, 0, count); + } + } + return response.toString("UTF-8"); + } + + private static long mostRecentEventTimeNanos() throws Exception { + List<Event> events = readSnetEvents(); + return events.isEmpty() ? 0 : events.get(events.size() - 1).getTimeNanos(); + } + + private static void assertDclLoggedSince(long previousEventNanos, String expectedSubTag, + String expectedNameHash, String expectedContentHash) throws Exception { + List<String> messages = + findMatchingEvents(previousEventNanos, expectedSubTag, expectedNameHash); + + assertHasDclLog(messages, expectedContentHash); + } + + private static void assertHasDclLog(List<String> messages, String expectedContentHash) { + assertWithMessage("Expected exactly one matching log entry").that(messages).hasSize(1); + assertThat(messages.get(0)).endsWith(expectedContentHash); + } + + private static void assertNoDclLoggedSince(long previousEventNanos, String expectedSubTag, + String expectedNameHash) throws Exception { + List<String> messages = + findMatchingEvents(previousEventNanos, expectedSubTag, expectedNameHash); + + assertWithMessage("Expected no matching log entries").that(messages).isEmpty(); + } + + private static List<String> findMatchingEvents(long previousEventNanos, String expectedSubTag, + String expectedNameHash) throws Exception { + List<String> messages = new ArrayList<>(); + + for (Event event : readSnetEvents()) { + if (event.getTimeNanos() <= previousEventNanos) { + continue; + } + + Object data = event.getData(); + if (!(data instanceof Object[])) { + continue; + } + Object[] fields = (Object[]) data; + + // We only care about DCL events that we generated. + String subTag = (String) fields[0]; + if (!expectedSubTag.equals(subTag)) { + continue; + } + int uid = (int) fields[1]; + if (uid != sMyUid) { + continue; + } + + String message = (String) fields[2]; + if (!message.startsWith(expectedNameHash)) { + continue; + } + + messages.add(message); + //assertThat(message).endsWith(expectedContentHash); + } + return messages; + } + + private static List<Event> readSnetEvents() throws Exception { + List<Event> events = new ArrayList<>(); + EventLog.readEvents(new int[] { SNET_TAG }, events); + return events; + } + + /** + * A class loader that does nothing useful, but importantly doesn't extend BaseDexClassLoader. + */ + private static class UnknownClassLoader extends ClassLoader { + UnknownClassLoader(ClassLoader parent) { + super(parent); + } + } +} diff --git a/tests/DynamicCodeLoggerIntegrationTests/src/cpp/com_android_dcl_Jni.cpp b/tests/DynamicCodeLoggerIntegrationTests/src/cpp/com_android_dcl_Jni.cpp new file mode 100644 index 000000000000..060888310b51 --- /dev/null +++ b/tests/DynamicCodeLoggerIntegrationTests/src/cpp/com_android_dcl_Jni.cpp @@ -0,0 +1,22 @@ +/* + * Copyright 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. + */ + +#include "jni.h" + +extern "C" jint JNI_OnLoad(JavaVM* /* vm */, void* /* reserved */) +{ + return JNI_VERSION_1_6; +} diff --git a/tests/DynamicCodeLoggerIntegrationTests/src/cpp/test_executable.cpp b/tests/DynamicCodeLoggerIntegrationTests/src/cpp/test_executable.cpp new file mode 100644 index 000000000000..ad025e696dec --- /dev/null +++ b/tests/DynamicCodeLoggerIntegrationTests/src/cpp/test_executable.cpp @@ -0,0 +1,20 @@ +/* + * Copyright 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. + */ + +int main() { + // This program just has to run, it doesn't need to do anything. So we don't. + return 0; +} |