Tip: Secure your code against the finalizer vulnerability

A pattern to prevent invalid classes from being created

Your Java code may be vulnerable to an exploit based on finalization. Learn how the exploit works and how to modify your code to prevent such an attack.

Neil D. Masson, Java Support Engineer, IBM

Neil MassonNeil Masson has worked on the development and support of the Java language for many years. Currently, he is focused on improving the quality and security of Java releases.



05 July 2011

Also available in Chinese Russian Japanese

Finalizers can cause a vulnerability in Java code when used to create objects. The exploit is a variation of the well-known technique of using a finalizer for resurrecting an object. When an object with a finalize() method becomes unreachable, it is put on a queue to be processed at some later time. This tip explains how the exploit works and shows how you can protect your code from it. All of the code examples are available for download.

The idea of finalizers is to allow a Java method to release any native resources that need to be returned to the operating system. Unfortunately, any Java code can be run in a finalizer, allowing code like Listing 1:

Listing 1. A resurrectable class
public class Zombie {
  static Zombie zombie;

  public void finalize() {
    zombie = this;
  }
}

When the Zombie finalizer is called, it takes the object being finalized — referenced by this — and stores it in the static zombie variable. Now the object is reachable again and cannot be garbage-collected.

A more insidious version of this code allows even a partially constructed object to be resurrected. Even if an object fails some criterion for correctness in its initializer, it can still be created by a finalizer, as in Listing 2:

Listing 2. Creating an illegal class
public class Zombie2 {
  static Zombie2 zombie;
  int value;

  public Zombie2(int value) {
    if(value < 0) {
      throw new IllegalArgumentException("Negative Zombie2 value");
    }
    this.value = value;
  }
  public void finalize() {
    zombie = this;
  }
}

In Listing 2, the effect of the check on the value argument is negated by the existence of the finalize() method.

How the attack works

Of course, nobody is likely to write code like Listing 2. But a vulnerability can arise if the class is subclassed, as in Listing 3:

Listing 3. A vulnerable class
class Vulnerable {
  Integer value = 0;
 
  Vulnerable(int value) {
    if(value <= 0) {
      throw new IllegalArgumentException("Vulnerable value must be positive");
    }
    this.value = value;
  }
  @Override
  public String toString() {
    return(value.toString());
  }
}

The Vulnerable class in Listing 3 is designed to prevent a nonpositive value of value from being set. This intent is subverted by the AttackVulnerable() method, as shown in Listing 4:

Listing 4. A class to subvert the Vulnerable class
class AttackVulnerable extends Vulnerable {
  static Vulnerable vulnerable;

  public AttackVulnerable(int value) {
    super(value);
  }

  public void finalize() {
    vulnerable = this;
  }

  public static void main(String[] args) {
    try {
      new AttackVulnerable(-1);
    } catch(Exception e) {
      System.out.println(e);
    }
    System.gc();
    System.runFinalization();
    if(vulnerable != null) {
      System.out.println("Vulnerable object " + vulnerable + " created!");
    }
  }
}

In the main() method of the AttackVulnerable class, an attempt is made to instantiate a new AttackVulnerable object. Because the value of value is out of range, an exception is thrown that is caught in the catch block. The System.gc() and System.runFinalization() calls encourage the VM to run a garbage-collection cycle and run any finalizers. These calls are not necessary for the attack to succeed, but they serve to demonstrate the end result of the attack, which is that a Vulnerable object is created that has an invalid value.

Running the test case gives the following result:

java.lang.IllegalArgumentException: Vulnerable value must be positive

Vulnerable object 0 created!

Why is the value of Vulnerable 0 and not -1? Notice that in the Vulnerable constructor in Listing 3, the assignment to value does not happen until after the argument check. So value has its initial value, which in this case is 0.

This kind of attack can even be used to bypass explicit security checks. For example, the Insecure class in Listing 5 is designed to throw a SecurityException if it's run under a SecurityManager and the caller does not have permission to write to the current directory:

Listing 5. Insecure class
import java.io.FilePermission;

public class Insecure {
  Integer value = 0;

  public Insecure(int value) {
    SecurityManager sm = System.getSecurityManager();
    if(sm != null) {
      FilePermission fp = new FilePermission("index", "write");
      sm.checkPermission(fp);
    }
    this.value = value;
  }
  @Override
  public String toString() {
    return(value.toString());
  }
}

The Insecure class Listing 5 can be attacked in the same way as before, as shown in the AttackInsecure class in Listing 6:

Listing 6. Attacking the Insecure class
public class AttackInsecure extends Insecure {
  static Insecure insecure;

  public AttackInsecure(int value) {
    super(value);
  }

  public void finalize() {
    insecure = this;
  }

  public static void main(String[] args) {
    try {
      new AttackInsecure(-1);
    } catch(Exception e) {
      System.out.println(e);
    }
    System.gc();
    System.runFinalization();
    if(insecure != null) {
      System.out.println("Insecure object " + insecure + " created!");
    }
  }
}

Running the code in Listing 6 under a SecurityManager gives the following output:

java -Djava.security.manager AttackInsecure
java.security.AccessControlException: Access denied (java.io.FilePermission index write)
Insecure object 0 created!

How to avoid the attack

Until the third edition of the Java Language Specification (JLS) was implemented in Java SE 6, the only ways to avoid the attack — using an initialized flag, prohibiting subclassing, or creating a final finalizer — were unsatisfactory solutions.

Using an initialized flag

One way to avoid the attack is to use an initialized flag, which is set to true once an object has been correctly created. Every method in the class first checks to see if initialized is set and throws an exception if it is not. This kind of coding is tiresome to write, is easy to omit by accident, and does not stop an attacker from subclassing the method.

Preventing subclassing

You can declare the class you're creating as final. This means that nobody can create a subclass of that class, which prevents the attack from working. However, this technique eliminates the flexibility of being able to extend the class in order to specialize it or add extra functionality.

Create a final finalizer

You can create a finalizer for the class you're creating and declare it as final. This means that no subclass of that class can declare a finalizer. The disadvantage of this approach is that the existence of the finalizer means that the object is kept alive longer than it otherwise would be.

A newer, better way

To make it easier to prevent this kind of attack without introducing an extra code or restrictions, the Java designers modified the JLS (see Resources) to state that if an exception is thrown in a constructor before java.lang.Object is constructed, the finalize() method of that method will not be executed.

But how can an exception be thrown before java.lang.Object is constructed? After all, the first line in any constructor must be a call to either this() or super(). If the constructor includes no such explicit call, a call to super() is implicitly added. So before an object is created, another object of the same class or of its superclass must be constructed. This eventually leads to the construction of java.lang.Object itself, and then the construction of all the subclasses, before any code from the method being constructed is executed.

To understand how an exception can be thrown before java.lang.Object is constructed, you need to understand the exact sequence of object construction. The JLS spells out the sequence explicitly.

When an object is created, the JVM:

  1. Allocates space for the object.
  2. Sets all the instance variables in the object to their default values. This includes the instance variables in the object's superclasses.
  3. Assigns the parameter variables for the object.
  4. Processes any explicit or implicit constructor invocation (a call to this() or super() in the constructor).
  5. Initializes variables in the class.
  6. Executes the rest of the constructor.

The key point is that the constructor's parameters are processed before any code inside the constructor is processed. This means that if you do your validation while processing the parameters, you can — by throwing an exception — prevent your class from being finalized.

This leads to a new version, shown in Listing 7, of Listing 3's Vulnerable class:

Listing 7. Invulnerable class
class Invulnerable {
  int value = 0;
 
  Invulnerable(int value) {
    this(checkValues(value));
    this.value = value;
  }

  private Invulnerable(Void checkValues) {}

  static Void checkValues(int value) {
    if(value <= 0) {
      throw new IllegalArgumentException("Invulnerable value must be positive");
    }
    return null;
  }

  @Override
  public String toString() {
    return(Integer.toString(value));
  }
}

In Listing 7, the public constructor for Invulnerable calls a private constructor that calls the checkValues method to create its parameter. This method is called before the constructor makes its call to construct its superclass, which is the constructor for Object. So if an exception is thrown in checkValues, then the Invulnerable object will not be finalized.

The code in Listing 8 tries to attack Invulnerable:

Listing 8. Attempt to subvert the Invulnerable class
class AttackInvulnerable extends Invulnerable {
  static Invulnerable vulnerable;

  public AttackInvulnerable(int value) {
    super(value);
  }

  public void finalize() {
    vulnerable = this;
  }

  public static void main(String[] args) {
    try {
      new AttackInvulnerable(-1);
    } catch(Exception e) {
      System.out.println(e);
    }
    System.gc();
    System.runFinalization();
    if(vulnerable != null) {
      System.out.println("Invulnerable object " + vulnerable + "
created!");
    } else {
      System.out.println("Attack failed");
    }
  }
}

with the addition of

    } else {
      System.out.println("Attack failed");

With Java 5, which was written to an older version of the JLS, an Invulnerable object is created:

java.lang.IllegalArgumentException: Invulnerable value must be positive
Invulnerable object 0 created!

Java SE 6 (starting with the general-availability release of Oracle's JVM and SR9 of IBM's JVM), follows the later specification, so the object is not created:

java.lang.IllegalArgumentException: Invulnerable value must be positive
Attack failed

Conclusion

Finalizers are an unfortunate feature of the Java language. Although the garbage collector can automatically reclaim any memory no longer used by Java objects, no such mechanism exists to recycle native resources such as native memory, file descriptors, or sockets. The standard libraries Java supplies that interface with these native resources usually have a close() method to allow a proper cleanup — but they also use finalizers to ensure no resource leak occurs if an object is not properly closed.

For other objects, finalizers are generally best avoided. There is no guarantee when a finalizer will be run, or even if it will be run at all. The existence of a finalizer means that an unreachable object cannot be garbage-collected until the finalizer has been run, and this object may be keeping yet more objects alive. This leads to an increase in the number of live objects and hence the in heap usage of the Java process.

The ability of a finalizer to revive an object destined for garbage collection is clearly an unintended consequence of the way that the finalization mechanism works. Newer implementations of the JVM now allow you to protect your code against the security implications of this effect.


Download

DescriptionNameSize
Code samples for this tipj-fv.zip.zip4KB

Resources

Learn

Get products and technologies

  • Evaluate IBM products in the way that suits you best: Download a product trial, try a product online, use a product in a cloud environment, or spend a few hours in the SOA Sandbox learning how to implement Service Oriented Architecture efficiently.

Discuss

  • Java security: Participate in the Java security forum on developerWorks.
  • Get involved in the developerWorks community. Connect with other developerWorks users while exploring the developer-driven blogs, forums, groups, and wikis.

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Select information in your profile (name, country/region, and company) is displayed to the public and will accompany any content you post. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Java technology on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology, Security
ArticleID=696773
ArticleTitle=Tip: Secure your code against the finalizer vulnerability
publish-date=07052011