Self-modifying code changes its own instructions during execution. This approach certainly offers advantages in practice, for example in terms of adaptability or optimization of the code. Because of the associated – sometimes significant – risks and challenges, it is generally not recommended altogether. The use of self-modifying code is particularly problematic for Java developers because it impacts predictability, readability, and maintainability of the code base. Additionally, the Java programming language does not natively support self-modifying code. Common Weakness Enumeration CWE-1123 (Excessive Use of Self-Modifying Code) describes the resulting threats.
Advertisement
Sven has been programming Java for over 15 years in industrial projects since 1996 and in industries such as automotive, aerospace, insurance, banking, United Nations and the World Bank around the world. For over 10 years he has been a speaker at conferences and community events from the US to New Zealand, worked as developer advocate for JFrog and Vaadin and regularly writes articles for IT magazines and technology portals. Apart from his core subject of Core Java, he deals with TDD and secure coding practices.
Risk from self-modifying code
Unexpected Behavior: Self-changing code can cause unpredictable program behavior, making it difficult to diagnose and fix errors.
Security Vulnerabilities: Code that modifies itself can be an attack vector for a variety of attacks, including injection attacks and malware.
Difficulty in Maintenance: Such code is difficult to read and understand, making it difficult to maintain and update.
performance issues: Self-changing code can result in performance degradation due to the additional overhead of making the change and interpreting it at runtime.
Examples of Risky Practices
Dynamic class loading: Java allows classes to be loaded at runtime using mechanisms such as reflection or custom class loaders. While dynamic class loading in itself is not inherently problematic or even wrong, excessive or blatant use of it can lead to behavior that takes on a life of its own.
Bytecode Manipulation: use libraries like asm Or the code itself may change due to Javassist (Java Programming Assistant) modifying the Java bytecode at runtime. Unless absolutely necessary, this practice is strongly discouraged.
reflection: Although reflection is a powerful feature, it can also be misused to modify private fields, methods, or classes. This results in behavior that is difficult to track and debug.
Code example of risky self-modifying behavior in Java using bytecode manipulation:

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
public class SelfModifyingExample {
public static void main(String() args) {
try {
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("TargetClass");
CtMethod m = cc.getDeclaredMethod("targetMethod");
m.insertBefore("{ System.out.println(\"Method modified at runtime\"); }");
cc.toClass();
TargetClass target = new TargetClass();
target.targetMethod();
} catch (Exception e) {
e.printStackTrace();
}
}
}
class TargetClass {
public void targetMethod() {
System.out.println("Original method execution");
}
}
in this instance TargetClass
-Method targetMethod
Changed at runtime to include an additional print
-Include instructions. Such changes may result in the risks described above.
Strategies to reduce risks
Avoiding runtime code changes: Design the system to minimize or eliminate the need for changes to runtime code.
Use design patterns: Use design patterns like Strategy or State patterns that enable behavior changes without changing the code at runtime.
Correct use of reflection: Use reflection sparingly – ideally only when no other viable solution exists. Use the document with caution.
Static Code Analysis: Use static code analysis tools to detect and prevent the introduction of self-modifying code.
Excessive use of self-modifying code in Java creates risks that can impact application security, maintainability, and performance. By following the best practices presented and using design patterns that promote flexibility and adaptability without changing the code at run time, the pitfalls associated with CWE-1123 can be avoided.
a reflection example
Reflection in Java allows introspection and manipulation of classes, fields, methods, and constructors at runtime. Heavy, excessive, or inappropriate use of reflection may result in the code defined in CWE-1123 being self-modifying. Possible consequences of this are: unexpected behavior, security vulnerabilities, and maintenance problems.
The following code example shows overuse of reflection to change the behavior of a class at run time – which can be seen as self-changing code:
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ReflectionExample {
public static void main(String() args) {
try {
// Original object creation
MyClass original = new MyClass();
original.printMessage();
// Using reflection to modify the behavior at runtime
Class> clazz = Class.forName("MyClass");
Method method = clazz.getDeclaredMethod("setMessage", String.class);
method.setAccessible(true);
// Modify the private field value using reflection
Field field = clazz.getDeclaredField("message");
field.setAccessible(true);
field.set(original, "Modified message");
// Verify the modification
original.printMessage();
} catch (Exception e) {
e.printStackTrace();
}
}
}
class MyClass {
private String message = "Original message";
public void printMessage() {
System.out.println(message);
}
private void setMessage(String message) {
this.message = message;
}
}
ReflectionExample
Class: Creates an instance of MyClass
And prints the original message. Example uses reflection to provide private field message and private method setMessage
From MyClass
To adapt. The value of the message field is then changed and the changed message is printed.
This example shows how reflection can change the behavior and state of an object at run time, causing the problems described in CWE-1123.
Mitigation Strategies for Reflection
Using Minimal Reflection: Avoid thinking unless absolutely necessary. Prefer alternative design patterns that allow flexibility without changing the code at runtime.
access control: Make sure that fields and methods that should not be modified are kept private and final whenever possible to prevent unintentional access.
Static Analysis Tools: Use static analysis tools to detect overuse of reflection and other risky practices in the code base.
Code Reviews: Perform thorough code review to detect and reduce the use of self-modifying code through reflection.
Reflection is a powerful tool in Java, but misuse can lead to risks associated with CWE-1123. By following best practices and minimizing the use of reflection to modify code at runtime, developers can maintain the security, predictability, and maintainability of their applications.
Example of dynamic class loading
In Java, dynamic class loading means the ability to load and unload classes at runtime. While this can be useful in some scenarios, excessive or inappropriate use can result in self-modifying code – and then lead to the risks described in CWE-1123: unexpected behavior, security vulnerabilities, and maintenance issues.
The code example shows the overuse of dynamic loading to change the behavior of a class at run time:
public class DynamicClassLoadingExample {
public static void main(String() args) {
try {
// Load the original class
ClassLoader classLoader = DynamicClassLoadingExample.class.getClassLoader();
Class> loadedClass = classLoader.loadClass("MyClass");
// Create an instance of the loaded class
Object instance = loadedClass.getDeclaredConstructor().newInstance();
// Invoke the original method
loadedClass.getMethod("printMessage").invoke(instance);
// Dynamically load the modified class
classLoader = new CustomClassLoader();
loadedClass = classLoader.loadClass("ModifiedClass");
// Create an instance of the modified class
instance = loadedClass.getDeclaredConstructor().newInstance();
// Invoke the modified method
loadedClass.getMethod("printMessage").invoke(instance);
} catch (Exception e) {
e.printStackTrace();
}
}
}
// Original class definition
class MyClass {
public void printMessage() {
System.out.println("Original message");
}
}
// Custom class loader to simulate loading a modified class
class CustomClassLoader extends ClassLoader {
@Override
public Class> loadClass(String name) throws ClassNotFoundException {
if ("ModifiedClass".equals(name)) {
// Define a modified version of MyClass at runtime
String modifiedClassName = "ModifiedClass";
String modifiedClassBody = "public class " + modifiedClassName + " {" +
" public void printMessage() {" +
" System.out.println(\"Modified message\");" +
" }" +
"}";
byte() classData = compileClass(modifiedClassName, modifiedClassBody);
return defineClass(modifiedClassName, classData, 0, classData.length);
}
return super.loadClass(name);
}
private byte() compileClass(String className, String classBody) {
// Simulate compiling the class body into bytecode (in a real scenario, use a compiler API)
// This is a placeholder for demonstration purposes
return classBody.getBytes();
}
}
DynamicClassLoadingExample
-Class: This loads a base class. MyClass
and calls them printMessage
Method Dynamically loads a modified version of a class using a custom class loader: ModifiedClass
Now it creates an instance of the changed class and calls it printMessage
The method that prints the second message.
Mitigation strategies for dynamic class loading
Avoiding dynamic class loading: Use dynamic class loading only when absolutely necessary and it cannot be circumvented by other design patterns.
Secure Class Loader: Make sure custom class loaders are safe and do not load untrusted or malicious classes.
Static Analysis Tools: Use static analysis tools to detect overuse of dynamic class loading and other risky practices in the code base.
Code Reviews: Perform thorough code review to identify and reduce the use of self-mutating code through dynamic class loading.
