6 Replies Latest reply on Oct 1, 2014 9:10 AM by David McElligott

    Trigger Load/Unload of Rules?

    David McElligott Newbie

      Hello,


      I am working on a hosting environment that will use Byteman as an AOP tool to report metrics from the JVM.

      One of the features of Byteman we would like to take advantage of is the live loading of new Byteman rules.

      Problem is that this communication takes place on an unsecure port at the loopback address, so we are worried that a malicious user on the box could tell the listener to load a malicious rule.

      Also we plan to have 100s of JVMs on a single host so managing the open ports for every JVM on the host could be a nightmare.

       

      I know that the listener can be told not to listen on a port with the javaagent parameter listener:false.

      I want to wrap Byteman in another agent that can trigger the loading and unloading of rules based on a few triggering methods.

      In Byteman is there a way to expose a call that I could wrap to trigger a load/unload?

        • 1. Re: Trigger Load/Unload of Rules?
          David McElligott Newbie

          I worked around this problem by copying the source from Main.java, importing some stock Byteman classes, and having it run my custom class based off of TransformListener.java that I can use to load new rules in anyway that I want to.


          AgentMain.java


          ...... Imports omitted
          
          import org.jboss.byteman.agent.Retransformer;
          
          public class AgentMain {
          
             private static ScriptReload scriptReload;
          
          ...... lines omitted
          
              public static void premain(String args, Instrumentation inst) {
          
                ...... lines omitted
          
                 if (allowRedefine && isRedefine) {
                    scriptReload.initialize((Retransformer)transformer); 
                    /*
                    *     transformerClazz = loader.loadClass("org.jboss.byteman.agent.Retransformer");     
                    *     Method method = transformerClazz.getMethod("initialize", Retransformer.class);     
                    *     method.invoke((Retransformer)transformer);     
                    */ 
                  }
          
          ...... lines omitted
          
          }
          
          
          
          
          
          
          
          


          ScriptReload.java


          import org.jboss.byteman.agent.Retransformer;
          import org.jboss.byteman.agent.Transformer;
          
          public class ScriptReload extends Thread {
            private static ScriptReload scriptReload = null;
            private Retransformer retransformer;
            private static List<String> scriptTexts;
            private static List<String> scriptNames;
          
            ScriptReload(Retransformer retransformer) {
                this.retransformer = retransformer;
                setDaemon(true);
            }
          
            public void run() {
                scriptTexts = Arrays.asList("RULE jabber\nCLASS java.lang.Thread\nMETHOD start()\nIF true\nDO traceln(\"*** start for thread: \"+ $0.getName())\nENDRULE\n");
                scriptNames = Arrays.asList("newRules.btm");
          
                PrintStream stdout = System.out;
                System.setOut(stdout);
                PrintWriter printWriter = new PrintWriter(stdout);
          
                while (true) {
                    try {
                        Thread.sleep(5000);
                        /*
                         * Get new script however you want
                         */
                        System.out.printf("Installing Scripts %s\n", scriptNames.toString());
                        retransformer.installScript(scriptTexts, scriptNames, printWriter);
                    } catch(InterruptedException e) {
                        e.printStackTrace();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
            public static synchronized boolean initialize(Retransformer retransformer) {
                if(scriptReload == null) {
                    scriptReload = new ScriptReload(retransformer);
                    scriptReload.start();
                }
            return true;
            }
          
          
          
          
          
          



          This is a rudementry example where the new rule to add is hardcoded, but I plan to add logic to add rules, purge rules, add jars, and other logic such as in the TransformListener. 

          This example is just to show that you can write your own way to reload rules without having to fork Byteman.

          • 2. Re: Trigger Load/Unload of Rules?
            Andrew Dinn Master

            Hi David,

             

            Nice work!

             

            Yeah, I deliberately coded Byteman so that the listener socket was the only point of entry to the dynamic capabilities of the Byteman agent. Even if you use class Submit in the JVM actually running the agent they still talk through the socket (there are no visible Java methods providing access to the agent load/unload capabilities or the Transformer/Instrumentation instances). That single access mechanism is good for some apps and not good for others. Your solution is a very nice way to provide an alternative access mechanism.

             

            There is one thing you might need to consider. Your agent will get loaded into the system classpath (that's how the JVM does it). So, this means your current definition of Main will load the imported Byteman classes into the system loader. This means you cannot then add the Byteman jar to the bootstrap classpath in order to instrument classes in java.lang etc. (the version of Transformer in the system classpath will clash with the version linked via the bootstrap classpath). If you look at my agent Main you will see that I only reference and invoke behaviour from other Byteman classes via reflection and only make the reflective calls after adding the byteman jar to the bootstrap path. You might want to think about doing the same.

            1 of 1 people found this helpful
            • 3. Re: Trigger Load/Unload of Rules?
              David McElligott Newbie

              Andrew, Thanks for the reply!

               

              I will try and changed it to only use reflection in the Main class and post my changes,

              hopefully you can take a look and let me know if you see any other problem areas.

              • 4. Re: Re: Trigger Load/Unload of Rules?
                David McElligott Newbie

                Alright, I managed to remove all of the imports of Byteman Classes from the Main class.

                The way I decided to do it was to reuse most of the code from Retranformer class, changing only the addTransformListener method to use my ScriptReload class.

                I also created a Gist with the omitted lines: https://gist.github.com/4be0097ed91ca23b315a.git.

                 

                Feedback is always appreciated!

                 

                AgentMain.java

                 

                package com.example.proj.agent;
                ... imports omitted
                public class AgentMain {
                    ... lines omitted
                    public static void premain(String args, Instrumentation inst) {
                        ... lines omitted
                
                        if (allowRedefine && isRedefine) {
                           transformerClazz = loader.loadClass("com.example.proj.agent.AgentRetransformer");
                           Constructor constructor.newInstance(new Object[] { inst, scriptPaths, scripts, isRedefine});
                        } else {
                            transformerClazz = loader.loadClass("org.jboss.byteman.agent.Transformer");
                            Constructor constructor = transformerClazz.getConstructor(Instrumentation.class, List.class, List.class, boolean.class);
                            transformer = (ClassFileTransformer)constructor.newInstance(new Object[] { inst, scriptPaths, scripts, isRedefine});
                        }
                        inst.addTransformer(transformer, true);
                     
                        if (allowRedefine && isRedefine) {
                            Method method = transformerClazz.getMethod("addTransformListener");
                            method.invoke(transformer);
                        }
                        ... lines omitted
                
                   }
                    ... lines omitted
                
                }
                }
                
                

                 

                AgentRetransformer.java

                 

                package com.example.proj.agent;
                
                ... imports omitted
                
                import org.jboss.byteman.agent.RuleScript;
                import org.jboss.byteman.agent.ScriptRepository;
                import org.jboss.byteman.agent.Transform;
                import org.jboss.byteman.agent.Transformer;
                
                public class AgentRetransformer extends Transformer {
                    ... lines omitted
                
                    public AgentRetransformer(Instrumentation inst, List<String> scriptPaths, List<String> scriptTexts, boolean isRedefine)
                            throws Exception
                    {
                        super(inst, scriptPaths, scriptTexts, isRedefine);
                    }
                
                    ... lines omitted
                
                    public void addTransformListener()
                    {
                        ScriptReload.initialize(this);
                    }
                
                    ... lines omitted
                
                }
                

                 

                ScriptReload.java


                package com.example.proj.agent;
                
                import org.jboss.byteman.agent.Retransformer;
                import org.jboss.byteman.agent.Transformer;
                
                public class ScriptReload extends Thread {
                  private static ScriptReload scriptReload = null;
                  private Retransformer retransformer;
                  private static List<String> scriptTexts;
                  private static List<String> scriptNames;
                
                  ScriptReload(Retransformer retransformer) {
                      this.retransformer = retransformer;
                      setDaemon(true);
                  }
                
                  public void run() {
                      scriptTexts = Arrays.asList("RULE jabber\nCLASS java.lang.Thread\nMETHOD start()\nIF true\nDO traceln(\"*** start for thread: \"+ $0.getName())\nENDRULE\n");
                      scriptNames = Arrays.asList("newRules.btm");
                
                      PrintStream stdout = System.out;
                      System.setOut(stdout);
                      PrintWriter printWriter = new PrintWriter(stdout);
                
                      while (true) {
                          try {
                              Thread.sleep(5000);
                              /*
                               * Get new script however you want
                               */
                              System.out.printf("Installing Scripts %s\n", scriptNames.toString());
                              retransformer.installScript(scriptTexts, scriptNames, printWriter);
                          } catch(InterruptedException e) {
                              e.printStackTrace();
                          } catch (Exception e) {
                              e.printStackTrace();
                          }
                      }
                  }
                  public static synchronized boolean initialize(Retransformer retransformer) {
                      if(scriptReload == null) {
                          scriptReload = new ScriptReload(retransformer);
                          scriptReload.start();
                      }
                  return true;
                  }
                
                
                • 5. Re: Trigger Load/Unload of Rules?
                  Andrew Dinn Master

                  Hi David,

                   

                  That looks like a very neat way to do what you want. However, I am not sure that all the details are right as you present them above. You have

                   

                    class AgentRetransformer extends Transformer


                  but

                   

                    ScriptReload(Retransformer retransformer) {

                      . . .

                   

                  Is your AgentRetransformer actually meant to extend Retransformer? Or is your ScriptReload class supposed to accept an AgentRetransformer. In the latter case I guess that means that you will have to cut and past quite a lot of the functionality of Retransformer into your own class. Clearly, it would be better if you could could adopt the former approach and just inherit the useful behaviour from my Retransformer, overriding the things you need to redefine.

                   

                  If you find that is difficult to achieve then let me know what is problematic. I might be able to make changes which simplify the job e.g. I could make certain members protected rather than private or I could even factor out the retransform functionality into an abstract superclass decoupling it from the socket-listener specific functions that I use to do load, unload and agent state management. I'm not promising I will do this but if your use case makes it clear that this is a useful and sensible factorization of functions then I would be very keen to implement it so that others can reuse my agent in the same way as you want to.

                   

                  Anyway, assuming some resolution of the issue above I think your design should now allow you to load both agents into the bootstrap loader so as to be able to instrument bootstrap classes. You will have to append both your agent jar and the byteman agent jar to the bootstrap loader path. If your premain still includes the args processing code from by my Main class (which searches args for "boot:/path/to/jar" and  adds the corresponding jars to the bootstrap path) then you coudl just add them on the java command line by specifying the agent options:

                   

                    -javaagent:/path/to/byteman.jar=boot:/path/to/byteman.jar,boot:/path/to/youragent.jar

                   

                  Thanks very much for coming up with this nice solution and don't hesitate to let me know if there is something I can do to help make it work better.

                   

                  regards,

                   

                   

                  Andrew Dinn

                  • 6. Re: Trigger Load/Unload of Rules?
                    David McElligott Newbie

                    Hey Andrew,

                     

                    I made the changes you suggested, inheriting the behavior from Retransformer is an obvious improvement.

                    Do you have any suggestions on the best way for me to add the new code snippets to maximize the usefulness of this discussion for other community members?

                     

                    I am glad you brought up the decoupling of the retransformer and socket-listener specific functions because that was my next question.

                    I would volunteer to work on it now, but I need to talk to my companies law department before I can take on any work in an official capacity.

                    Ideally, I would much rather contribute then spend my time on a work-around!

                     

                    My suggestion would be to keep your socket-listener as a default from the Java agent listener param (e.g. listener=true ), but possibly allowing the path to a jar in the Java agent listener param as a replacement for your socket-listener (e.g. listener=%CUSTOM_LISTENER_HOME%/File-Listener.jar ) that can be parsed out.

                     

                    Some use cases off the top of my head.

                    • Rules can be loaded from any source
                    • Custom security implementations can be used to fit individual needs
                    • No longer relies on ports
                    • Makes it possible to negate the threat of a malicious insider

                     

                    Hopefully by letting developers code their own listeners you can spend more time on the Byteman core functionality we all love so much!

                     

                    Let me know your thoughts on the matter and I will update you when I hear back from the law department about contributing to the project.

                     

                     

                    Thanks for all your great feedback!

                     

                    David McElligott