byte-buddy icon indicating copy to clipboard operation
byte-buddy copied to clipboard

Byte buddy not able to pass modify args to original method

Open ranavivek04 opened this issue 8 months ago • 3 comments

   private final Level leastSpecificLevelToEncrypt;

    public LogInterceptor(Level leastSpecificLevelToEncrypt) {
        this.leastSpecificLevelToEncrypt = leastSpecificLevelToEncrypt;
    }

    @RuntimeType
    public Object intercept(@AllArguments Object[] args, @Origin Method method, @SuperCall Callable<?> superMethod) throws Exception {
        System.out.println("Intercepting log method");

        // Log the object, method, and parameters
        System.out.println("Invoking method: " + method.getName());
        System.out.println("On object: " + args[0]);
        System.out.println("With parameters:");
        for (int i = 0; i < args.length; i++) {
            System.out.println("  args[" + i + "]: " + (args[i] != null ? args[i].getClass().getName() : "null") + " = " + args[i]);
        }

        Level level = (Level) args[0];
        Message message = (Message) args[4];
        Throwable throwable = (Throwable) args[5];

        // Apply encryption logic if necessary
        if (LogMessageEncryptor.getInstance().isInitialized() && level.isLessSpecificThan(leastSpecificLevelToEncrypt)) {
            System.out.println("Encrypting log message: " + message.getFormattedMessage());
            EncodedMessage encodedMessage = new EncodedMessage(Encode.forJava(message.getFormattedMessage()));
            args[4] = LogMessageEncryptor.INSTANCE.encrypt(encodedMessage, throwable); // Replace the message with the encrypted message
            //args[4] = new CustomMessageWrapper(LogMessageEncryptor.INSTANCE.encrypt(encodedMessage, throwable));
            args[5] = null; // Clear the throwable
            System.out.println("args[4] after replacement: " + args[4].getClass().getName());
            System.out.println("args[4] content: " + ((Message) args[4]).getFormattedMessage());
        }

        // Log the invocation of the original method
        System.out.println("LogInterceptor: Invoking original method with modified arguments");
        // 
        // System.out.println("Encrypted message: " + args[4].toString());
        
        for (int i = 0; i < args.length; i++) {
    System.out.println("  args[" + i + "]: " + (args[i] != null ? args[i].getClass().getName() : "null") + " = " + args[i]);
}
        // Invoke the original method with the modified arguments
        // return method.invoke(args[0], args);
        System.out.println("Before method.invoke: args[4] = " + args[4].getClass().getName());
        Object result = superMethod.call(); //method.invoke(args[0], args);
        System.out.println("After method.invoke: args[4] = " + args[4].getClass().getName());
        return result;
    }
}

Final logs printed on the client console aren't encrypted. However, if I use the cglib base implementation instead of bytebuddy with the same logic, the logs are encrypted. Below are the logs with bytebuddy:

Intercepting log method Invoking method: log On object: STATUS With parameters: args[0]: org.apache.logging.log4j.Level = STATUS args[1]: null = null args[2]: java.lang.String = org.apache.logging.log4j.spi.AbstractLogger args[3]: null = null args[4]: org.apache.logging.log4j.message.ParameterizedMessage = ParameterizedMessage[messagePattern=NLE started on port {}, stringArgs=[8088], throwable=null] args[5]: null = null Encrypting log message: NLE started on port 8088 encrypt called for message: NLE started on port 8088 args[4] after replacement: x.x.x.toolkit.log4j2.EncryptedMessage EncryptedMessage.getFormattedMessage() called args[4] content: encrypted log >>EKquNaISFbSXLN6vu3a5lMSEn84TGLxJe1gsJn0oi5w=<< Key >>Q7nPqeWHCg6xPBa3hGl6FPnp7p7wxqeWG2myhL3g5heVG9vapITvwcJgKyqggcy7t8EaiJmEtUR4r71cD4Z/YwQ7snNk0ri5xkHaUDMW7nCNDJ80QvA7CEWG6Vimtz7NmvyepB1wMX0DKmVYccj6Z1qMQOXea/dP+f2Gdnuly6MCU0Esy2QGLskEFH/MoO/a93gG8zCSEk9Yr9w7iN8j/9AtqZ/gAGbnUgWWmtbeeBvHiGe5lkERFX1MWIlxG12zZEm7wOLQv2Xajj6IA8/ZOPXrcBZXJsvLDT1aDZYmyNFSBkBE4hhnI05caGN0lHVp/rEEMUkZqNWF+H6UsmFsHQ==<< LogInterceptor: Invoking original method with modified arguments args[0]: org.apache.logging.log4j.Level = STATUS args[1]: null = null args[2]: java.lang.String = org.apache.logging.log4j.spi.AbstractLogger args[3]: null = null args[4]: x.x.x.toolkit.log4j2.EncryptedMessage = x.x.x.log4j2.EncryptedMessage@1c55237e args[5]: null = null Before method.invoke: args[4] = x.x.x.toolkit.log4j2.EncryptedMessage After method.invoke: args[4] = x.x.x.toolkit.log4j2.EncryptedMessage 2025-05-13 09:29:10,375 STATUS [ main] c.n.n.mStatusLogger : NLE started on port 8088 logs from cglib based implementation:

EncryptedMessage.getFormattedMessage() called log: Object: c.n.n.mStatusLogger:STATUS in AsyncContext@179d3b25 log: Args: [STATUS, null, org.apache.logging.log4j.spi.AbstractLogger, null, x.x.x.toolkit.log4j2.EncryptedMessage@3fde2209, null] EncryptedMessage.getFormattedMessage() called EncryptedMessage.getFormattedMessage() called 2025-05-13 11:31:00,354 STATUS [ main] c.n.n.ApplicationReadyStatusLogger : encrypted log >>QBwj5PL1zdNFHW45smJVhGnU1YmR4SWRGHykyCk+o58=<< Key >>kxv8NnIdX7HP5rg9OQcXHpm0p0eAMEeyVR23zEt3jeLDKYagQ7f/xggACKIopWHKm+Jq3vN3XcMuvL+49Qlap5FNzmU2HLOVqjlhhcLfOrVYmITNH/7gyvZyyih4mERDOxOpqyq8u1yl6fShBnNtbB00hdhFvFHqKSpPMpqhBEjmf/p+JP46yNOhHBvVqbeT3Wh6RKYqy4FH6miTJnDm+8BzujNdvFmh4gk3MXHvWzJW78+nkcMf6fkAP/PEf/WhE46FjASZfRSOmq+7uWz4ZBHDOE18y4Y1VXHPUZsCoCSo3dWZFankWREGla1POgzFBCkjTskhYm18wWnw23kWhw==<< EncryptedMessage.getFormattedMessage() called Log encryption enabled: true

The issue seems to occur when the ByteBuddy-based implementation passes x.x.x.toolkit.log4j2.EncryptedMessage as an argument to the original method, which is the log method. In this case, the EncryptedMessage.getFormattedMessage() method isn't called by log4j2. However, when the same encrypted message is passed by the CGLIB-based implementation, the EncryptedMessage.getFormattedMessage() method is called, and the final logs printed on the client console are encrypted.

Here is how I'm creating EnhancedLogger:

Class<? extends Logger> enhancedLoggerClass = new ByteBuddy()
                    .subclass(loggerClass)
                    .method(ElementMatchers.named("logMessage")
                            .and(ElementMatchers.takesArguments(String.class, Level.class, Marker.class, Message.class, Throwable.class)))
                    .intercept(MethodDelegation.to(new LogMessageInterceptor(leastSpecificLevelToEncrypt)))
                    .method(ElementMatchers.named("log")
                            .and(ElementMatchers.takesArguments(Level.class, Marker.class, String.class, StackTraceElement.class, Message.class, Throwable.class)))
                    .intercept(MethodDelegation.to(new LogInterceptor(leastSpecificLevelToEncrypt)))
                    .make()
                    .load(loggerClass.getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
                    .getLoaded();

I think bytebuddy isn't able to pass modified args to original method. Isn't bytebuddy designed to support this

ranavivek04 avatar May 14 '25 05:05 ranavivek04

Method delegation is designed to avoid this. Try out Advice where you inline code into the instrumented method and operate on the actual arguments.

raphw avatar May 15 '25 08:05 raphw

@raphw Do you mean to say if I use Advice instead of delegation , I would be able to pass modified args to original method? If possible, sample could would be of great help. Thanks!

ranavivek04 avatar May 17 '25 12:05 ranavivek04

Yes, you would need to use Advice for this.

raphw avatar May 18 '25 09:05 raphw