Ejemplo n.º 1
0
  /**
   * return a checker object which can be used to retrieve the super and interfaces of a class from
   * its name and classloader, identifying it from the Class instance if it the class is already
   * loaded otherwise loading the corresponding bytecode and parsing it to obtain the relevant
   * details.
   *
   * @param name the name of the superclass being checked
   * @param baseLoader the class loader of the subclass's bytecode
   * @return the requisite checker or null if the class does not need to be checked or cannot be
   *     loaded
   */
  public org.jboss.byteman.agent.check.ClassChecker getClassChecker(
      String name, ClassLoader baseLoader) {
    // we would like to just do this
    // Class superClazz = baseLoader.loadClass(name)
    // and then access the details using methods of Class
    // however, this fails because we are in the middle of transforming the subclass and the
    // classloader
    // may not have loaded the super. if we force a load now then transforms will not be performed
    // on
    // the super class. this may cause us to miss the chance to apply rule injection into the super

    ClassLoader loader = baseLoader;
    Class clazz = loadCache.lookupClass(name, loader);

    if (clazz != null) {
      return new org.jboss.byteman.agent.check.LoadedClassChecker(clazz);
    }

    // ok, instead try loading the bytecode as a resource - user-defined loaders may not support
    // this but
    // at least the JVM system and boot loaders should

    String resourceName = name.replace('.', '/') + ".class";
    try {
      InputStream is;
      if (baseLoader != null) {
        is = baseLoader.getResourceAsStream(resourceName);
      } else {
        is = ClassLoader.getSystemClassLoader().getResourceAsStream(resourceName);
      }
      if (is != null) {
        int length = is.available();
        int count = 0;
        byte[] bytecode = new byte[length];
        while (count < length) {
          int read = is.read(bytecode, count, length - count);
          if (read < 0) {
            throw new IOException("unexpected end of file");
          }
          count += read;
        }
        return new org.jboss.byteman.agent.check.BytecodeChecker(bytecode);
      } else {
        // throw new IOException("unable to load bytecode for for class " + name);
        if (isVerbose()) {
          System.out.println(
              "Transformer.getClassChecker : unable to load bytecode for for class " + name);
        }
        return null;
      }
    } catch (IOException e) {
      // log the exception and return null
      e.printStackTrace();
      return null;
    }
  }
Ejemplo n.º 2
0
  /**
   * The implementation of this method may transform the supplied class file and return a new
   * replacement class file.
   *
   * <p>Once a transformer has been registered with {@link
   * java.lang.instrument.Instrumentation#addTransformer Instrumentation.addTransformer}, the
   * transformer will be called for every new class definition and every class redefinition. The
   * request for a new class definition is made with {@link ClassLoader#defineClass
   * ClassLoader.defineClass}. The request for a class redefinition is made with {@link
   * java.lang.instrument.Instrumentation#redefineClasses Instrumentation.redefineClasses} or its
   * native equivalents. The transformer is called during the processing of the request, before the
   * class file bytes have been verified or applied.
   *
   * <p>If the implementing method determines that no transformations are needed, it should return
   * <code>null</code>. Otherwise, it should create a new <code>byte[]</code> array, copy the input
   * <code>classfileBuffer</code> into it, along with all desired transformations, and return the
   * new array. The input <code>classfileBuffer</code> must not be modified.
   *
   * <p>In the redefine case, the transformer must support the redefinition semantics. If a class
   * that the transformer changed during initial definition is later redefined, the transformer must
   * insure that the second class output class file is a legal redefinition of the first output
   * class file.
   *
   * <p>If the transformer believes the <code>classFileBuffer</code> does not represent a validly
   * formatted class file, it should throw an <code>IllegalClassFormatException</code>. Subsequent
   * transformers will still be called and the load or redefine will still be attempted. Throwing an
   * <code>IllegalClassFormatException</code> thus has the same effect as returning null but
   * facilitates the logging or debugging of format corruptions.
   *
   * @param originalLoader the defining loader of the class to be transformed, may be <code>null
   *     </code> if the bootstrap loader
   * @param className the name of the class in the internal form of fully qualified class and
   *     interface names as defined in <i>The Java Virtual Machine Specification</i>. For example,
   *     <code>"java/util/List"</code>.
   * @param classBeingRedefined if this is a redefine, the class being redefined, otherwise <code>
   *     null</code>
   * @param protectionDomain the protection domain of the class being defined or redefined
   * @param classfileBuffer the input byte buffer in class file format - must not be modified
   * @return a well-formed class file buffer (the result of the transform), or <code>null</code> if
   *     no transform is performed.
   * @throws java.lang.instrument.IllegalClassFormatException if the input does not represent a
   *     well-formed class file
   * @see java.lang.instrument.Instrumentation#redefineClasses
   */
  public byte[] transform(
      ClassLoader originalLoader,
      String className,
      Class<?> classBeingRedefined,
      ProtectionDomain protectionDomain,
      byte[] classfileBuffer)
      throws IllegalClassFormatException {
    boolean enabled = true;
    ClassLoader loader = originalLoader;
    try {
      enabled = Rule.disableTriggersInternal();

      byte[] newBuffer = classfileBuffer;
      // we only transform certain classes -- we do allow bootstrap classes whose loader is null
      // but we exclude byteman classes and java.lang classes
      String internalName = TypeHelper.internalizeClass(className);

      if (isBytemanClass(internalName) || !isTransformable(internalName)) {
        return null;
      }

      // we will need the super class name any outer class name and the name of the interfaces the
      // class implements

      ClassChecker checker = getClassChecker(newBuffer); // new ClassChecker(newBuffer);

      if (checker == null || checker.isInterface()) {
        return null;
      }

      /*
      if (checker.hasOuterClass()) {
          // we don't transform inner classes for now
          // TODO -- see if we can match and transform inner classes via the outer class
          return null;
      }
      */

      // TODO-- reconsider this as it is a bit dodgy as far as security is concerned

      if (loader == null) {
        loader = ClassLoader.getSystemClassLoader();
      }

      // if we need to traverse the interfaces then we have a DAG to deal with so
      // we had better find a way to avoid doing things twice

      LinkedList<String> toVisit = null;
      HashSet<String> visited = null;

      // ok, we need to check whether there are any class scripts associated with this class and if
      // so
      // we will consider transforming the byte code

      // TODO -- there are almost certainly concurrency issues to deal with here if rules are being
      // loaded/unloaded

      newBuffer = tryTransform(newBuffer, internalName, loader, internalName, false);

      int dotIdx = internalName.lastIndexOf('.');

      if (dotIdx > 0) {
        newBuffer =
            tryTransform(
                newBuffer, internalName, loader, internalName.substring(dotIdx + 1), false);
      }

      if (scriptRepository.checkInterfaces()) {
        // now we need to do the same for any interface scripts
        // n.b. resist the temptation to call classBeingRedefined.getInterfaces() as this will
        // cause the class to be resolved, losing any changes we install

        // we need to check the transitive closure of the binary links
        // Class implements Interface and Interface extends Interface for this class
        // which in general is a DAG.

        toVisit = new LinkedList<String>();
        visited = new HashSet<String>();

        // we start with the original list of implemented interfaces

        int interfaceCount = checker.getInterfaceCount();
        for (int i = 0; i < interfaceCount; i++) {
          String interfaceName = checker.getInterface(i);
          toVisit.add(interfaceName);
        }

        // ok now check each interface in turn while pushing its super interfaces
        // until we no longer have any new interfaces to check

        while (!toVisit.isEmpty()) {
          String interfaceName = toVisit.pop();
          String internalInterfaceName = TypeHelper.internalizeClass(interfaceName);
          if (!visited.contains(interfaceName)) {
            // avoid visiting  this interface again
            visited.add(interfaceName);
            // now see if we have any rules for this interface
            newBuffer = tryTransform(newBuffer, internalName, loader, internalInterfaceName, true);
            dotIdx = internalInterfaceName.lastIndexOf('.');
            if (dotIdx >= 0) {
              newBuffer =
                  tryTransform(
                      newBuffer,
                      internalName,
                      loader,
                      internalInterfaceName.substring(dotIdx + 1),
                      true);
            }
            // check the extends list of this interface for new interfaces to consider
            ClassChecker newChecker = getClassChecker(interfaceName, originalLoader);
            if (newChecker != null) {
              interfaceCount = newChecker.getInterfaceCount();
              for (int i = 0; i < interfaceCount; i++) {
                interfaceName = newChecker.getInterface(i);
                toVisit.add(interfaceName);
              }
            }
          }
        }
      }

      // checking supers is expensive so we obey the switch which disables it

      if (!skipOverrideRules()) {
        // ok, now check the superclass for this class and so on

        String superName = checker.getSuper();

        while (superName != null) {
          // we need to check the super class structure
          // n.b. we use the original loader here because we don't want to search the system loader
          // when we have a class in the bootstrap loader
          checker = getClassChecker(superName, originalLoader);

          if (checker == null || checker.hasOuterClass()) {
            // we don't transform inner classes for now
            // TODO -- see if we can match and transform inner classes via the outer class
            break;
          }

          newBuffer = tryTransform(newBuffer, internalName, loader, superName, false, true);
          dotIdx = superName.lastIndexOf('.');
          if (dotIdx > 0) {
            newBuffer =
                tryTransform(
                    newBuffer, internalName, loader, superName.substring(dotIdx + 1), false, true);
          }

          if (scriptRepository.checkInterfaces()) {
            // we need to do another DAG visit but only for interfaces not already considered

            int interfaceCount = checker.getInterfaceCount();
            for (int i = 0; i < interfaceCount; i++) {
              String interfaceName = checker.getInterface(i);
              toVisit.add(interfaceName);
            }

            // ok now check each interface in turn while pushing its super interfaces
            // until we no longer have any new interfaces to check

            while (!toVisit.isEmpty()) {
              String interfaceName = toVisit.pop();
              String internalInterfaceName = TypeHelper.internalizeClass(interfaceName);
              if (!visited.contains(interfaceName)) {
                // avoid visiting  this interface again
                visited.add(interfaceName);
                // now see if we have any rules for this interface
                newBuffer =
                    tryTransform(
                        newBuffer, internalName, loader, internalInterfaceName, true, true);
                dotIdx = interfaceName.lastIndexOf('.');
                if (dotIdx >= 0) {
                  newBuffer =
                      tryTransform(
                          newBuffer,
                          internalName,
                          loader,
                          internalInterfaceName.substring(dotIdx + 1),
                          true,
                          true);
                }
                // check the extends list of this interface for new interfaces to consider
                ClassChecker newChecker = getClassChecker(interfaceName, originalLoader);
                if (newChecker != null) {
                  interfaceCount = newChecker.getInterfaceCount();
                  for (int i = 0; i < interfaceCount; i++) {
                    interfaceName = newChecker.getInterface(i);
                    toVisit.add(interfaceName);
                  }
                }
              }
            }
          }
          // move on to the next super
          superName = checker.getSuper();
        }
      }

      if (newBuffer != classfileBuffer) {
        // see if we need to dump the transformed bytecode for checking
        maybeDumpClass(internalName, newBuffer);
        newBuffer =
            maybeVerifyTransformedBytes(originalLoader, internalName, protectionDomain, newBuffer);
        return newBuffer;
      } else {
        return null;
      }
    } finally {
      if (enabled) {
        Rule.enableTriggersInternal();
      }
    }
  }