diff options
| author | 2017-06-05 18:03:23 +0000 | |
|---|---|---|
| committer | 2017-06-05 18:03:24 +0000 | |
| commit | 81c769436a89b25c781eb2da882f11fd8d11f84d (patch) | |
| tree | eb7d10728981bf0ee5f16502ce949d823030504d /test | |
| parent | 8c4fd14f714900190bd2024aa07495e874520da6 (diff) | |
| parent | 07f0621463e7b480c86ddba3e72d3fb9f0ae820f (diff) | |
Merge "Fix redefinition related use-after-free bug"
Diffstat (limited to 'test')
| -rw-r--r-- | test/988-redefine-use-after-free/expected.txt | 0 | ||||
| -rw-r--r-- | test/988-redefine-use-after-free/info.txt | 13 | ||||
| -rwxr-xr-x | test/988-redefine-use-after-free/run | 17 | ||||
| -rw-r--r-- | test/988-redefine-use-after-free/src-ex/DexCacheSmash.java | 155 | ||||
| -rw-r--r-- | test/988-redefine-use-after-free/src-ex/art/Redefinition.java | 91 | ||||
| -rw-r--r-- | test/988-redefine-use-after-free/src/Main.java | 54 |
6 files changed, 330 insertions, 0 deletions
diff --git a/test/988-redefine-use-after-free/expected.txt b/test/988-redefine-use-after-free/expected.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/988-redefine-use-after-free/expected.txt diff --git a/test/988-redefine-use-after-free/info.txt b/test/988-redefine-use-after-free/info.txt new file mode 100644 index 0000000000..2b683dd75e --- /dev/null +++ b/test/988-redefine-use-after-free/info.txt @@ -0,0 +1,13 @@ +Regression test for b/62237378 + +It was possible for the JVMTI class redefinition to encounter a use-after-free +bug if there had been an attempted redefinition that failed due to a +verification error in the same class loader. Actually encountering the bug +required that a later redefinition happen to get the same native pointer for its +dex-file as the failed redefinition. + +Hitting this use-after-free can cause many strange outcomes, from CHECK failures +to segfaults to incorrect redefinition failures (for example on buggy builds +this test will fail a DCHECK on debug builds, segfault on x86_64 hosts and have +redefinition of LDexCacheSmash$Transform; erroneously fail with +JVMTI_ERROR_FAILS_VERIFICATION on 32 bit hosts). diff --git a/test/988-redefine-use-after-free/run b/test/988-redefine-use-after-free/run new file mode 100755 index 0000000000..c6e62ae6cd --- /dev/null +++ b/test/988-redefine-use-after-free/run @@ -0,0 +1,17 @@ +#!/bin/bash +# +# Copyright 2016 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. + +./default-run "$@" --jvmti diff --git a/test/988-redefine-use-after-free/src-ex/DexCacheSmash.java b/test/988-redefine-use-after-free/src-ex/DexCacheSmash.java new file mode 100644 index 0000000000..2193a631cd --- /dev/null +++ b/test/988-redefine-use-after-free/src-ex/DexCacheSmash.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 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. + */ + +import art.Redefinition; +import java.util.Base64; + +public class DexCacheSmash { + static class Transform { + public void foo() {} + public void bar() {} + public String getId() { + return "TRANSFORM_INITIAL"; + } + } + + static class Transform2 { + public String getId() { + return "TRANSFORM2_INITIAL"; + } + } + + /** + * A base64 encoding of the dex/class file of the Transform class above. + */ + static final Redefinition.CommonClassDefinition TRANSFORM_INITIAL = + new Redefinition.CommonClassDefinition(Transform.class, + Base64.getDecoder().decode( + "yv66vgAAADQAFwoABAAPCAAQBwASBwAVAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1i" + + "ZXJUYWJsZQEAA2ZvbwEAA2JhcgEABWdldElkAQAUKClMamF2YS9sYW5nL1N0cmluZzsBAApTb3Vy" + + "Y2VGaWxlAQASRGV4Q2FjaGVTbWFzaC5qYXZhDAAFAAYBABFUUkFOU0ZPUk1fSU5JVElBTAcAFgEA" + + "F0RleENhY2hlU21hc2gkVHJhbnNmb3JtAQAJVHJhbnNmb3JtAQAMSW5uZXJDbGFzc2VzAQAQamF2" + + "YS9sYW5nL09iamVjdAEADURleENhY2hlU21hc2gAIAADAAQAAAAAAAQAAAAFAAYAAQAHAAAAHQAB" + + "AAEAAAAFKrcAAbEAAAABAAgAAAAGAAEAAAATAAEACQAGAAEABwAAABkAAAABAAAAAbEAAAABAAgA" + + "AAAGAAEAAAAUAAEACgAGAAEABwAAABkAAAABAAAAAbEAAAABAAgAAAAGAAEAAAAVAAEACwAMAAEA" + + "BwAAABsAAQABAAAAAxICsAAAAAEACAAAAAYAAQAAABcAAgANAAAAAgAOABQAAAAKAAEAAwARABMA" + + "CA=="), + Base64.getDecoder().decode( + "ZGV4CjAzNQDhg9CfghG1SRlLClguRuFYsqihr4F7NsGQAwAAcAAAAHhWNBIAAAAAAAAAAOQCAAAS" + + "AAAAcAAAAAcAAAC4AAAAAgAAANQAAAAAAAAAAAAAAAUAAADsAAAAAQAAABQBAABcAgAANAEAAKgB" + + "AACwAQAAxAEAAMcBAADiAQAA8wEAABcCAAA3AgAASwIAAF8CAAByAgAAfQIAAIACAACNAgAAkgIA" + + "AJcCAACeAgAApAIAAAMAAAAEAAAABQAAAAYAAAAHAAAACAAAAAsAAAACAAAABQAAAAAAAAALAAAA" + + "BgAAAAAAAAAAAAEAAAAAAAAAAQANAAAAAAABAA4AAAAAAAAADwAAAAQAAQAAAAAAAAAAAAAAAAAE" + + "AAAAAAAAAAEAAACYAQAAzgIAAAAAAAACAAAAvwIAAMUCAAABAAEAAQAAAKsCAAAEAAAAcBAEAAAA" + + "DgABAAEAAAAAALACAAABAAAADgAAAAEAAQAAAAAAtQIAAAEAAAAOAAAAAgABAAAAAAC6AgAAAwAA" + + "ABoACQARAAAANAEAAAAAAAAAAAAAAAAAAAY8aW5pdD4AEkRleENhY2hlU21hc2guamF2YQABTAAZ" + + "TERleENhY2hlU21hc2gkVHJhbnNmb3JtOwAPTERleENhY2hlU21hc2g7ACJMZGFsdmlrL2Fubm90" + + "YXRpb24vRW5jbG9zaW5nQ2xhc3M7AB5MZGFsdmlrL2Fubm90YXRpb24vSW5uZXJDbGFzczsAEkxq" + + "YXZhL2xhbmcvT2JqZWN0OwASTGphdmEvbGFuZy9TdHJpbmc7ABFUUkFOU0ZPUk1fSU5JVElBTAAJ" + + "VHJhbnNmb3JtAAFWAAthY2Nlc3NGbGFncwADYmFyAANmb28ABWdldElkAARuYW1lAAV2YWx1ZQAT" + + "AAcOABUABw4AFAAHDgAXAAcOAAICAREYAQIDAgwECBAXCgAAAQMAgIAEwAIBAdgCAQHsAgEBgAMO" + + "AAAAAAAAAAEAAAAAAAAAAQAAABIAAABwAAAAAgAAAAcAAAC4AAAAAwAAAAIAAADUAAAABQAAAAUA" + + "AADsAAAABgAAAAEAAAAUAQAAAxAAAAEAAAA0AQAAASAAAAQAAABAAQAABiAAAAEAAACYAQAAAiAA" + + "ABIAAACoAQAAAyAAAAQAAACrAgAABCAAAAIAAAC/AgAAACAAAAEAAADOAgAAABAAAAEAAADkAgAA")); + + /** + * A base64 encoding of the following (invalid) class. + * + * .class LDexCacheSmash$Transform2; + * .super Ljava/lang/Object; + * .source "DexCacheSmash.java" + * + * # annotations + * .annotation system Ldalvik/annotation/EnclosingClass; + * value = LDexCacheSmash; + * .end annotation + * + * .annotation system Ldalvik/annotation/InnerClass; + * accessFlags = 0x8 + * name = "Transform2" + * .end annotation + * + * + * # direct methods + * .method constructor <init>()V + * .registers 1 + * + * .prologue + * .line 26 + * invoke-direct {p0}, Ljava/lang/Object;-><init>()V + * + * return-void + * .end method + * + * + * # virtual methods + * .method public getId()Ljava/lang/String; + * .registers 2 + * + * .prologue + * .line 28 + * # NB Fails verification due to this function not returning a String. + * return-void + * .end method + */ + static final Redefinition.CommonClassDefinition TRANSFORM2_INVALID = + new Redefinition.CommonClassDefinition(Transform2.class, + Base64.getDecoder().decode( + "yv66vgAAADQAEwcAEgcAEQEABjxpbml0PgEAAygpVgEABENvZGUKAAIAEAEAD0xpbmVOdW1iZXJU" + + "YWJsZQEABWdldElkAQAUKClMamF2YS9sYW5nL1N0cmluZzsBAApTb3VyY2VGaWxlAQASRGV4Q2Fj" + + "aGVTbWFzaC5qYXZhAQAMSW5uZXJDbGFzc2VzBwAPAQAKVHJhbnNmb3JtMgEADURleENhY2hlU21h" + + "c2gMAAMABAEAEGphdmEvbGFuZy9PYmplY3QBABhEZXhDYWNoZVNtYXNoJFRyYW5zZm9ybTIAIAAB" + + "AAIAAAAAAAIAAAADAAQAAQAFAAAAHQABAAEAAAAFKrcABrEAAAABAAcAAAAGAAEAAAAaAAEACAAJ" + + "AAEABQAAABkAAQABAAAAAbEAAAABAAcAAAAGAAEAAAAcAAIACgAAAAIACwAMAAAACgABAAEADQAO" + + "AAg="), + Base64.getDecoder().decode( + "ZGV4CjAzNQCFcegr6Ns+I7iEF4uLRkUX4yGrLhP6soEgAwAAcAAAAHhWNBIAAAAAAAAAAHQCAAAP" + + "AAAAcAAAAAcAAACsAAAAAgAAAMgAAAAAAAAAAAAAAAMAAADgAAAAAQAAAPgAAAAIAgAAGAEAABgB" + + "AAAgAQAANAEAADcBAABTAQAAZAEAAIgBAACoAQAAvAEAANABAADcAQAA3wEAAOwBAADzAQAA+QEA" + + "AAMAAAAEAAAABQAAAAYAAAAHAAAACAAAAAoAAAACAAAABQAAAAAAAAAKAAAABgAAAAAAAAAAAAEA" + + "AAAAAAAAAAAMAAAABAABAAAAAAAAAAAAAAAAAAQAAAAAAAAAAQAAACACAABmAgAAAAAAAAY8aW5p" + + "dD4AEkRleENhY2hlU21hc2guamF2YQABTAAaTERleENhY2hlU21hc2gkVHJhbnNmb3JtMjsAD0xE" + + "ZXhDYWNoZVNtYXNoOwAiTGRhbHZpay9hbm5vdGF0aW9uL0VuY2xvc2luZ0NsYXNzOwAeTGRhbHZp" + + "ay9hbm5vdGF0aW9uL0lubmVyQ2xhc3M7ABJMamF2YS9sYW5nL09iamVjdDsAEkxqYXZhL2xhbmcv" + + "U3RyaW5nOwAKVHJhbnNmb3JtMgABVgALYWNjZXNzRmxhZ3MABWdldElkAARuYW1lAAV2YWx1ZQAC" + + "AwILBAgNFwkCAgEOGAEAAAAAAAIAAAAJAgAAAAIAABQCAAAAAAAAAAAAAAAAAAAaAAcOABwABw4A" + + "AAABAAEAAQAAADACAAAEAAAAcBACAAAADgACAAEAAAAAADUCAAABAAAADgAAAAEBAICABLwEAQHU" + + "BA4AAAAAAAAAAQAAAAAAAAABAAAADwAAAHAAAAACAAAABwAAAKwAAAADAAAAAgAAAMgAAAAFAAAA" + + "AwAAAOAAAAAGAAAAAQAAAPgAAAACIAAADwAAABgBAAAEIAAAAgAAAAACAAADEAAAAgAAABACAAAG" + + "IAAAAQAAACACAAADIAAAAgAAADACAAABIAAAAgAAADwCAAAAIAAAAQAAAGYCAAAAEAAAAQAAAHQC" + + "AAA=")); + + public static void run() throws Exception { + try { + Redefinition.doMultiClassRedefinition(TRANSFORM2_INVALID); + } catch (Exception e) { + if (!e.getMessage().endsWith("JVMTI_ERROR_FAILS_VERIFICATION")) { + throw new Error( + "Unexpected error: Expected failure due to JVMTI_ERROR_FAILS_VERIFICATION", e); + } + } + // Doing this redefinition after a redefinition that failed due to FAILS_VERIFICATION could + // cause a use-after-free of the Transform2's DexCache by the redefinition code if it happens + // that the native pointer of the art::DexFile created for the Transform redefinition aliases + // the one created for Transform2's failed redefinition. + // + // Due to the order of checks performed by the redefinition code FAILS_VERIFICATION is the only + // failure mode that can cause Use-after-frees in this way. + // + // This should never throw any exceptions (except perhaps OOME in very strange circumstances). + Redefinition.doMultiClassRedefinition(TRANSFORM_INITIAL); + } +} diff --git a/test/988-redefine-use-after-free/src-ex/art/Redefinition.java b/test/988-redefine-use-after-free/src-ex/art/Redefinition.java new file mode 100644 index 0000000000..56d2938a01 --- /dev/null +++ b/test/988-redefine-use-after-free/src-ex/art/Redefinition.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 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 art; + +import java.util.ArrayList; +// Common Redefinition functions. Placed here for use by CTS +public class Redefinition { + public static final class CommonClassDefinition { + public final Class<?> target; + public final byte[] class_file_bytes; + public final byte[] dex_file_bytes; + + public CommonClassDefinition(Class<?> target, byte[] class_file_bytes, byte[] dex_file_bytes) { + this.target = target; + this.class_file_bytes = class_file_bytes; + this.dex_file_bytes = dex_file_bytes; + } + } + + // A set of possible test configurations. Test should set this if they need to. + // This must be kept in sync with the defines in ti-agent/common_helper.cc + public static enum Config { + COMMON_REDEFINE(0), + COMMON_RETRANSFORM(1), + COMMON_TRANSFORM(2); + + private final int val; + private Config(int val) { + this.val = val; + } + } + + public static void setTestConfiguration(Config type) { + nativeSetTestConfiguration(type.val); + } + + private static native void nativeSetTestConfiguration(int type); + + // Transforms the class + public static native void doCommonClassRedefinition(Class<?> target, + byte[] classfile, + byte[] dexfile); + + public static void doMultiClassRedefinition(CommonClassDefinition... defs) { + ArrayList<Class<?>> classes = new ArrayList<>(); + ArrayList<byte[]> class_files = new ArrayList<>(); + ArrayList<byte[]> dex_files = new ArrayList<>(); + + for (CommonClassDefinition d : defs) { + classes.add(d.target); + class_files.add(d.class_file_bytes); + dex_files.add(d.dex_file_bytes); + } + doCommonMultiClassRedefinition(classes.toArray(new Class<?>[0]), + class_files.toArray(new byte[0][]), + dex_files.toArray(new byte[0][])); + } + + public static void addMultiTransformationResults(CommonClassDefinition... defs) { + for (CommonClassDefinition d : defs) { + addCommonTransformationResult(d.target.getCanonicalName(), + d.class_file_bytes, + d.dex_file_bytes); + } + } + + public static native void doCommonMultiClassRedefinition(Class<?>[] targets, + byte[][] classfiles, + byte[][] dexfiles); + public static native void doCommonClassRetransformation(Class<?>... target); + public static native void setPopRetransformations(boolean pop); + public static native void popTransformationFor(String name); + public static native void enableCommonRetransformation(boolean enable); + public static native void addCommonTransformationResult(String target_name, + byte[] class_bytes, + byte[] dex_bytes); +} diff --git a/test/988-redefine-use-after-free/src/Main.java b/test/988-redefine-use-after-free/src/Main.java new file mode 100644 index 0000000000..d88c471a07 --- /dev/null +++ b/test/988-redefine-use-after-free/src/Main.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 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. + */ + +import java.lang.reflect.*; + +public class Main { + public static final String TEST_NAME = "988-redefine-use-after-free"; + public static final int REPS = 1000; + public static final int STEP = 100; + + public static void main(String[] args) throws Exception { + for (int i = 0; i < REPS; i += STEP) { + runSeveralTimes(STEP); + } + } + + public static ClassLoader getClassLoaderFor(String location) throws Exception { + try { + Class<?> class_loader_class = Class.forName("dalvik.system.PathClassLoader"); + Constructor<?> ctor = class_loader_class.getConstructor(String.class, ClassLoader.class); + return (ClassLoader)ctor.newInstance(location + "/" + TEST_NAME + "-ex.jar", + Main.class.getClassLoader()); + } catch (ClassNotFoundException e) { + // Running on RI. Use URLClassLoader. + return new java.net.URLClassLoader( + new java.net.URL[] { new java.net.URL("file://" + location + "/classes-ex/") }); + } + } + + // Run the redefinition several times on a single class-loader to try to trigger the + // Use-after-free bug b/62237378 + public static void runSeveralTimes(int times) throws Exception { + ClassLoader c = getClassLoaderFor(System.getenv("DEX_LOCATION")); + + Class<?> klass = (Class<?>)c.loadClass("DexCacheSmash"); + Method m = klass.getDeclaredMethod("run"); + for (int i = 0 ; i < times; i++) { + m.invoke(null); + } + } +} |