Java tutorial
/* * Copyright 2009-2012 Michael Dalton * * 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 jtaint; import java.util.ArrayList; import java.util.List; import org.objectweb.asm.ClassAdapter; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.Label; import org.objectweb.asm.Opcodes; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Type; import org.objectweb.asm.commons.AdviceAdapter; /** Add taint tracking to String objects. Methods that create Strings * are instrumented to propagate taint. Additionally, new constructors * and methods are provided to initialize taint and query taint information, * respectively. */ public class StringAdapter extends StubAdapter implements Opcodes { private static final List methodList; private String className; private int version; static { List l = new ArrayList(); l.add(new MethodDecl(ACC_PUBLIC, "concat", "(Ljava/lang/String;)Ljava/lang/String;")); l.add(new MethodDecl(ACC_PUBLIC, "replace", "(CC)Ljava/lang/String;")); l.add(new MethodDecl(ACC_PUBLIC, "subSequence", "(II)Ljava/lang/CharSequence;")); l.add(new MethodDecl(ACC_PUBLIC, "substring", "(I)Ljava/lang/String;")); l.add(new MethodDecl(ACC_PUBLIC, "substring", "(II)Ljava/lang/String;")); l.add(new MethodDecl(ACC_PUBLIC, "toLowerCase", "(Ljava/util/Locale;)Ljava/lang/String;")); l.add(new MethodDecl(ACC_PUBLIC, "toLowerCase", "()Ljava/lang/String;")); l.add(new MethodDecl(ACC_PUBLIC, "toUpperCase", "(Ljava/util/Locale;)Ljava/lang/String;")); l.add(new MethodDecl(ACC_PUBLIC, "toUpperCase", "()Ljava/lang/String;")); l.add(new MethodDecl(ACC_PUBLIC, "trim", "()Ljava/lang/String;")); methodList = l; } public StringAdapter(ClassVisitor cv) { super(cv, methodList); } public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { this.className = name; this.version = version; super.visit(version, access, name, signature, superName, interfaces); } /** Wrap String methods that create new Strings so that taint is propagated. * Wrappers call the original method, and then call a helper routine in * jtaint with the original String object, method arguments, and the * return value from the original method. The jtaint helper then * returns a String with the appropriate taint value. */ private void buildTaintWrapper(MethodVisitor mv, String name, String desc) { mv.visitCode(); Type[] t = Type.getArgumentTypes(desc); mv.visitVarInsn(ALOAD, 0); int l = 1; for (int i = 0; i < t.length; l += t[i].getSize(), i++) mv.visitVarInsn(t[i].getOpcode(ILOAD), l); mv.visitMethodInsn(INVOKEVIRTUAL, className, ByteCodeUtil.internalName(name), desc); mv.visitVarInsn(ASTORE, l); mv.visitVarInsn(ALOAD, 0); l = 1; for (int i = 0; i < t.length; l += t[i].getSize(), i++) mv.visitVarInsn(t[i].getOpcode(ILOAD), l); mv.visitVarInsn(ALOAD, l); /* We call the jtaint helper method by passing the arguments * this_object, arg1, arg2, ..., argN, result_object, so append * and prepend an extra java/lang/String object to the arg list. */ Type[] u = new Type[t.length + 2]; Type stringType = Type.getObjectType("java/lang/String"); u[0] = u[u.length - 1] = stringType; System.arraycopy(t, 0, u, 1, t.length); String helperDesc = Type.getMethodDescriptor(stringType, u); mv.visitMethodInsn(INVOKESTATIC, "jtaint/StringUtil", name, helperDesc); mv.visitInsn(ARETURN); mv.visitMaxs(l + 1, l + 1); mv.visitEnd(); } /** Force to{Upper/Lower}Case() to return * to{Upper/LowerCase}(java.util.Locale.getDefault()) */ private void replaceChangeCase(MethodVisitor mv, String name) { mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn(INVOKESTATIC, "java/util/Locale", "getDefault", "()Ljava/util/Locale;"); mv.visitMethodInsn(INVOKEVIRTUAL, className, name, "(Ljava/util/Locale;)Ljava/lang/String;"); mv.visitInsn(ARETURN); mv.visitMaxs(2, 1); mv.visitEnd(); } /** Force substring(begin) to return substring(begin, this.count) */ private void replaceSubstring(MethodVisitor mv) { mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitVarInsn(ILOAD, 1); mv.visitVarInsn(ALOAD, 0); mv.visitFieldInsn(GETFIELD, className, "count", "I"); mv.visitMethodInsn(INVOKEVIRTUAL, className, "substring", "(II)Ljava/lang/String;"); mv.visitInsn(ARETURN); mv.visitMaxs(3, 2); mv.visitEnd(); } /** Force subSequence(begin, end) to return substring(begin, end) */ private void replaceSubSequence(MethodVisitor mv) { mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitVarInsn(ILOAD, 1); mv.visitVarInsn(ILOAD, 2); mv.visitMethodInsn(INVOKEVIRTUAL, className, "substring", "(II)Ljava/lang/String;"); mv.visitInsn(ARETURN); mv.visitMaxs(3, 3); mv.visitEnd(); } /** Force the StringBuilder/StringBuffer constructors to convert to * a String and invoke the String constructor */ private void replaceConstructorStringBuilder(MethodVisitor mv, String name) { mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitVarInsn(ALOAD, 1); mv.visitMethodInsn(INVOKEVIRTUAL, name, "toString", "()Ljava/lang/String;"); mv.visitMethodInsn(INVOKESPECIAL, className, "<init>", "(Ljava/lang/String;)V"); mv.visitInsn(RETURN); mv.visitMaxs(2, 2); mv.visitEnd(); } private void copyField(MethodVisitor mv, String name, String desc) { mv.visitVarInsn(ALOAD, 0); mv.visitVarInsn(ALOAD, 1); mv.visitFieldInsn(GETFIELD, className, name, desc); mv.visitFieldInsn(PUTFIELD, className, name, desc); } /** Force the String constructor to just copy all fields */ private void replaceConstructorString(MethodVisitor mv) { mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V"); copyField(mv, "offset", "I"); copyField(mv, "count", "I"); copyField(mv, "value", "[C"); copyField(mv, ByteCodeUtil.internalName("tainted"), "Z"); mv.visitInsn(RETURN); mv.visitMaxs(2, 2); mv.visitEnd(); } public MethodVisitor visitMethod(final int access, final String name, final String desc, String signature, String[] exceptions) { final MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); if ("<init>".equals(name)) { if ("(Ljava/lang/String;)V".equals(desc)) { replaceConstructorString(mv); return null; } else if ("(Ljava/lang/StringBuilder;)V".equals(desc)) { replaceConstructorStringBuilder(mv, "java/lang/StringBuilder"); return null; } else if ("(Ljava/lang/StringBuffer;)V".equals(desc)) { replaceConstructorStringBuilder(mv, "java/lang/StringBuffer"); return null; } else return new TaintedInitAdapter(mv, className, access, desc); } if (!methodList.contains(new MethodDecl(access, name, desc))) return mv; /* Check for methods we replace */ if ("subSequence".equals(name)) { replaceSubSequence(mv); return null; } if ("substring".equals(name) && "(I)Ljava/lang/String;".equals(desc)) { replaceSubstring(mv); return null; } if (("toLowerCase".equals(name) || "toUpperCase".equals(name)) && "()Ljava/lang/String;".equals(desc)) { replaceChangeCase(mv, name); return null; } /* Otherwise rename and wrap */ buildTaintWrapper(mv, name, desc); return super.visitMethod(access, ByteCodeUtil.internalName(name), desc, signature, exceptions); } /** Export package-private java.lang methods for use by jtaint helper * functions. */ private void buildExportWrapper(ClassVisitor cv, String exportOwner, String name, String desc) { MethodVisitor mv = cv.visitMethod(ACC_PUBLIC + ACC_STATIC, ByteCodeUtil.internalName(name), desc, null, null); mv.visitCode(); Type[] t = Type.getArgumentTypes(desc); int l = 0; for (int i = 0; i < t.length; l += t[i].getSize(), i++) mv.visitVarInsn(t[i].getOpcode(ILOAD), l); mv.visitMethodInsn(INVOKESTATIC, exportOwner, name, desc); mv.visitInsn(Type.getReturnType(desc).getOpcode(IRETURN)); mv.visitMaxs(l, l); mv.visitEnd(); } /** Create new method that compares its argument to the package-private * constant Character.ERROR. This must be exported for jtaint helper * methods. Equivalent to the following Java code: * * public static boolean isError(int c) { * return c == Character.ERROR; * } */ private void addIsErrorMethod(ClassVisitor cv) { boolean isError = false; MethodVisitor mv = cv.visitMethod(ACC_PUBLIC + ACC_STATIC, ByteCodeUtil.internalName("isError"), "(I)Z", null, null); mv.visitCode(); mv.visitVarInsn(ILOAD, 0); /* Test to see if java/lang/Character uses ERROR or CHAR_ERROR * If ERROR cannot be found, an getDeclaredFields throws an exception */ try { Character.class.getDeclaredField("ERROR"); isError = true; } catch (Throwable th) { /* ignore */ } if (isError) mv.visitFieldInsn(GETSTATIC, "java/lang/Character", "ERROR", "I"); else mv.visitFieldInsn(GETSTATIC, "java/lang/Character", "CHAR_ERROR", "C"); Label l = new Label(); mv.visitJumpInsn(IF_ICMPEQ, l); mv.visitInsn(ICONST_0); mv.visitInsn(IRETURN); mv.visitLabel(l); if (version == V1_6) mv.visitFrame(F_SAME, 0, null, 0, null); mv.visitInsn(ICONST_1); mv.visitInsn(IRETURN); mv.visitMaxs(2, 1); mv.visitEnd(); } /** Create a new method that returns a Taint object representing the taint * for this String. Equivalent to the following Java code: * * public Taint taint() { * if (!tainted) { * return null; * } else { * return jtaint.StringUtil.stringToTaint(value, count); * } * } */ private void addTaintMethod(ClassVisitor cv) { MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, ByteCodeUtil.internalName("taint"), "()Ljtaint/Taint;", null, null); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitFieldInsn(GETFIELD, className, ByteCodeUtil.internalName("tainted"), "Z"); Label l = new Label(); mv.visitJumpInsn(IFNE, l); mv.visitInsn(ACONST_NULL); mv.visitInsn(ARETURN); mv.visitLabel(l); if (version == V1_6) mv.visitFrame(F_SAME, 0, null, 0, null); mv.visitVarInsn(ALOAD, 0); mv.visitFieldInsn(GETFIELD, className, "value", "[C"); mv.visitVarInsn(ALOAD, 0); mv.visitFieldInsn(GETFIELD, className, "count", "I"); mv.visitMethodInsn(INVOKESTATIC, "jtaint/StringUtil", "stringToTaint", "([CI)Ljtaint/Taint;"); mv.visitInsn(ARETURN); mv.visitMaxs(4, 1); mv.visitEnd(); } /** Add a new constructor to a create a (partially or fully) tainted * String. Equivalent to the following Java code: * * public String(String original, Taint t) { * super(); * this.count = original.count; * * if (!t.isTainted()) { * this.offset = original.offset; * this.value = original.value; * this.tainted = original.tainted; * return * } * * this.offset = 0; * this.value = jtaint.StringUtil.taintToString(original, t) * if (this.value.length == this.count) * this.tainted = false; * else * this.tainted = true; * return; * The final check (if value.length == count) is true only when an error * occurs during the execution of taintToString */ private void addConstructor(ClassVisitor cv) { MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, "<init>", "(Ljava/lang/String;Ljtaint/Taint;)V", null, null); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V"); copyField(mv, "count", "I"); Label l0 = new Label(); mv.visitVarInsn(ALOAD, 2); mv.visitMethodInsn(INVOKEVIRTUAL, "jtaint/Taint", "isTainted", "()Z"); mv.visitJumpInsn(IFNE, l0); /* Taint object is actually untainted, copy all fields and return */ copyField(mv, "offset", "I"); copyField(mv, "value", "[C"); copyField(mv, ByteCodeUtil.internalName("tainted"), "Z"); mv.visitInsn(RETURN); mv.visitLabel(l0); if (version == V1_6) mv.visitFrame(F_SAME, 0, null, 0, null); mv.visitVarInsn(ALOAD, 0); mv.visitInsn(ICONST_0); mv.visitFieldInsn(PUTFIELD, className, "offset", "I"); mv.visitVarInsn(ALOAD, 0); mv.visitVarInsn(ALOAD, 1); mv.visitVarInsn(ALOAD, 2); mv.visitMethodInsn(INVOKESTATIC, "jtaint/StringUtil", "taintToString", "(Ljava/lang/String;Ljtaint/Taint;)[C"); mv.visitInsn(DUP_X1); mv.visitFieldInsn(PUTFIELD, className, "value", "[C"); mv.visitInsn(ARRAYLENGTH); mv.visitVarInsn(ALOAD, 0); mv.visitFieldInsn(GETFIELD, className, "count", "I"); Label l1 = new Label(); mv.visitJumpInsn(IF_ICMPEQ, l1); mv.visitVarInsn(ALOAD, 0); mv.visitInsn(ICONST_1); mv.visitFieldInsn(PUTFIELD, className, ByteCodeUtil.internalName("tainted"), "Z"); mv.visitInsn(RETURN); mv.visitLabel(l1); if (version == V1_6) mv.visitFrame(F_SAME, 0, null, 0, null); mv.visitVarInsn(ALOAD, 0); mv.visitInsn(ICONST_0); mv.visitFieldInsn(PUTFIELD, className, ByteCodeUtil.internalName("tainted"), "Z"); mv.visitInsn(RETURN); mv.visitMaxs(3, 3); mv.visitEnd(); } public void onEndBuildStubs() { String internalField = ByteCodeUtil.internalName("tainted"); cv.visitField(ACC_PRIVATE + ACC_TRANSIENT + ACC_FINAL, internalField, "Z", null, null).visitEnd(); ByteCodeUtil.buildGetter(cv, className, internalField, "Z", ACC_FINAL, ByteCodeUtil.internalName("isTainted")); /* Build export wrappers for jtaint/StringUtil */ if (VmInfo.version() >= VmInfo.VERSION1_5) { buildExportWrapper(cv, "java/lang/ConditionalSpecialCasing", "toLowerCaseEx", "(Ljava/lang/String;ILjava/util/Locale;)I"); buildExportWrapper(cv, "java/lang/ConditionalSpecialCasing", "toUpperCaseEx", "(Ljava/lang/String;ILjava/util/Locale;)I"); buildExportWrapper(cv, "java/lang/ConditionalSpecialCasing", "toLowerCaseCharArray", "(Ljava/lang/String;ILjava/util/Locale;)[C"); buildExportWrapper(cv, "java/lang/ConditionalSpecialCasing", "toUpperCaseCharArray", "(Ljava/lang/String;ILjava/util/Locale;)[C"); } else /* VmInfo.version() == VmInfo.VERSION1_4 */ { buildExportWrapper(cv, "java/lang/Character", "toUpperCaseEx", "(C)C"); buildExportWrapper(cv, "java/lang/Character", "toUpperCaseCharArray", "(C)[C"); } addIsErrorMethod(cv); addTaintMethod(cv); addConstructor(cv); } /** By default, initialize the new boolean field 'tainted' to false for * all constructors. */ private static class TaintedInitAdapter extends AdviceAdapter { private int savedThis; public TaintedInitAdapter(MethodVisitor mv, String owner, int access, String desc) { super(mv, owner, access, "<init>", desc); } protected void visitSuper(final int opcode, final String owner, final String name, final String desc) { mv.visitMethodInsn(opcode, owner, name, desc); savedThis = newLocal(Type.getObjectType(className)); mv.visitVarInsn(ALOAD, 0); mv.visitVarInsn(ASTORE, savedThis); } protected void onMethodExit(final int opcode) { if (!superInitialized || opcode != RETURN) return; mv.visitVarInsn(ALOAD, savedThis); mv.visitInsn(ICONST_0); mv.visitFieldInsn(PUTFIELD, className, ByteCodeUtil.internalName("tainted"), "Z"); } public void visitMaxs(int nStack, int nLocals) { super.visitMaxs(nStack + 2, nLocals); }; }; public static InstrumentationBuilder builder() { return Builder.getInstance(); } private static class Builder implements InstrumentationBuilder { private static final Builder b = new Builder(); public static InstrumentationBuilder getInstance() { return b; } public ClassVisitor build(ClassVisitor cv) { return new StringAdapter(cv); } } }